Skip to main content

sqry_core/query/builder/
query_builder.rs

1//! Main [`QueryBuilder`] implementation
2
3use std::sync::Arc;
4
5use super::{BuildError, ConditionBuilder, RegexBuilder};
6use crate::query::registry::FieldRegistry;
7use crate::query::types::{
8    Expr, FieldType, Operator, Query as QueryAST, RegexFlags, RegexValue, Span, Value,
9};
10
11/// Builder for constructing type-safe queries.
12///
13/// # Example
14///
15/// ```ignore
16/// use sqry_core::query::builder::QueryBuilder;
17///
18/// let query = QueryBuilder::kind("function")
19///     .and(QueryBuilder::lang("rust"))
20///     .and_not(QueryBuilder::name_matches("test.*"))
21///     .build()?;
22/// ```
23#[derive(Clone, Debug)]
24#[must_use = "QueryBuilder does nothing until .build() is called"]
25pub struct QueryBuilder {
26    /// The expression being built
27    expr: BuilderExpr,
28    /// Accumulated validation errors (lazy validation)
29    errors: Vec<BuildError>,
30}
31
32/// Internal expression representation during building
33#[derive(Clone, Debug)]
34enum BuilderExpr {
35    /// Single condition
36    Condition(ConditionBuilder),
37    /// AND of multiple expressions
38    And(Vec<QueryBuilder>),
39    /// OR of multiple expressions
40    Or(Vec<QueryBuilder>),
41    /// Negation of expression
42    Not(Box<QueryBuilder>),
43    /// Empty builder (for chaining from `new()`)
44    Empty,
45}
46
47// ============================================================================
48// Constructor Methods
49// ============================================================================
50
51impl QueryBuilder {
52    /// Create empty builder for chaining
53    pub fn new() -> Self {
54        Self {
55            expr: BuilderExpr::Empty,
56            errors: Vec::new(),
57        }
58    }
59
60    // ========================================================================
61    // Node Identity Fields
62    // ========================================================================
63
64    /// Filter by symbol kind (function, method, class, etc.)
65    pub fn kind(value: impl Into<String>) -> Self {
66        Self::condition("kind", Operator::Equal, Value::String(value.into()))
67    }
68
69    /// Filter by multiple symbol kinds (OR)
70    pub fn kind_any(values: &[&str]) -> Self {
71        Self::any(values.iter().map(|v| Self::kind(*v)).collect())
72    }
73
74    /// Filter by symbol name (exact match)
75    pub fn name(value: impl Into<String>) -> Self {
76        Self::condition("name", Operator::Equal, Value::String(value.into()))
77    }
78
79    /// Filter by symbol name (regex match with default flags)
80    pub fn name_matches(pattern: impl Into<String>) -> Self {
81        let regex = RegexValue {
82            pattern: pattern.into(),
83            flags: RegexFlags::default(),
84        };
85        Self::condition("name", Operator::Regex, Value::Regex(regex))
86    }
87
88    /// Filter by symbol name (regex match with custom flags via closure)
89    ///
90    /// # Example
91    ///
92    /// ```ignore
93    /// // Case-insensitive name matching
94    /// QueryBuilder::name_matches_with("test.*", |rb| rb.case_insensitive())
95    /// ```
96    pub fn name_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
97    where
98        F: FnOnce(RegexBuilder) -> RegexBuilder,
99    {
100        let builder = RegexBuilder::new(pattern);
101        let configured = configure(builder);
102        Self::condition(
103            "name",
104            Operator::Regex,
105            Value::Regex(configured.into_regex_value()),
106        )
107    }
108
109    /// Filter by programming language
110    pub fn lang(value: impl Into<String>) -> Self {
111        Self::condition("lang", Operator::Equal, Value::String(value.into()))
112    }
113
114    /// Filter by programming language (alias for lang)
115    pub fn language(value: impl Into<String>) -> Self {
116        Self::lang(value)
117    }
118
119    // ========================================================================
120    // Location Fields
121    // ========================================================================
122
123    /// Filter by file path (exact or glob match)
124    pub fn path(value: impl Into<String>) -> Self {
125        Self::condition("path", Operator::Equal, Value::String(value.into()))
126    }
127
128    /// Filter by file path (alias for path)
129    pub fn file(value: impl Into<String>) -> Self {
130        Self::path(value)
131    }
132
133    /// Filter by file path (regex match with default flags)
134    pub fn path_matches(pattern: impl Into<String>) -> Self {
135        let regex = RegexValue {
136            pattern: pattern.into(),
137            flags: RegexFlags::default(),
138        };
139        Self::condition("path", Operator::Regex, Value::Regex(regex))
140    }
141
142    /// Filter by file path (regex match with custom flags via closure)
143    ///
144    /// # Example
145    ///
146    /// ```ignore
147    /// // Case-insensitive path matching
148    /// QueryBuilder::path_matches_with(".*test.*", |rb| rb.case_insensitive())
149    /// ```
150    pub fn path_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
151    where
152        F: FnOnce(RegexBuilder) -> RegexBuilder,
153    {
154        let builder = RegexBuilder::new(pattern);
155        let configured = configure(builder);
156        Self::condition(
157            "path",
158            Operator::Regex,
159            Value::Regex(configured.into_regex_value()),
160        )
161    }
162
163    /// Filter by repository
164    pub fn repo(value: impl Into<String>) -> Self {
165        Self::condition("repo", Operator::Equal, Value::String(value.into()))
166    }
167
168    // ========================================================================
169    // Hierarchy Fields
170    // ========================================================================
171
172    /// Filter by parent symbol
173    pub fn parent(value: impl Into<String>) -> Self {
174        Self::condition("parent", Operator::Equal, Value::String(value.into()))
175    }
176
177    // ========================================================================
178    // Content Fields
179    // ========================================================================
180
181    /// Filter by text content (regex only, default flags)
182    pub fn text_matches(pattern: impl Into<String>) -> Self {
183        let regex = RegexValue {
184            pattern: pattern.into(),
185            flags: RegexFlags::default(),
186        };
187        Self::condition("text", Operator::Regex, Value::Regex(regex))
188    }
189
190    /// Filter by text content (regex with custom flags via closure)
191    ///
192    /// # Example
193    ///
194    /// ```ignore
195    /// // Multi-line text matching
196    /// QueryBuilder::text_matches_with("^pub fn.*$", |rb| rb.multiline())
197    /// ```
198    pub fn text_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
199    where
200        F: FnOnce(RegexBuilder) -> RegexBuilder,
201    {
202        let builder = RegexBuilder::new(pattern);
203        let configured = configure(builder);
204        Self::condition(
205            "text",
206            Operator::Regex,
207            Value::Regex(configured.into_regex_value()),
208        )
209    }
210
211    // ========================================================================
212    // Relation Predicates
213    // ========================================================================
214
215    /// Filter symbols that call the specified symbol
216    pub fn callers(symbol: impl Into<String>) -> Self {
217        Self::condition("callers", Operator::Equal, Value::String(symbol.into()))
218    }
219
220    /// Filter symbols called by the specified symbol
221    pub fn callees(symbol: impl Into<String>) -> Self {
222        Self::condition("callees", Operator::Equal, Value::String(symbol.into()))
223    }
224
225    /// Filter symbols that import the specified module
226    pub fn imports(module: impl Into<String>) -> Self {
227        Self::condition("imports", Operator::Equal, Value::String(module.into()))
228    }
229
230    /// Filter symbols that export something
231    pub fn exports(value: impl Into<String>) -> Self {
232        Self::condition("exports", Operator::Equal, Value::String(value.into()))
233    }
234
235    /// Filter symbols with the specified return type
236    pub fn returns(type_name: impl Into<String>) -> Self {
237        Self::condition("returns", Operator::Equal, Value::String(type_name.into()))
238    }
239
240    /// Filter symbols that reference the specified symbol
241    pub fn references(symbol: impl Into<String>) -> Self {
242        Self::condition("references", Operator::Equal, Value::String(symbol.into()))
243    }
244
245    // ========================================================================
246    // Scope Predicates (P2-34)
247    // ========================================================================
248
249    /// Filter by scope (file, module, class, function, block)
250    ///
251    /// This targets the core `scope` field (enum type).
252    pub fn scope(value: impl Into<String>) -> Self {
253        Self::condition("scope", Operator::Equal, Value::String(value.into()))
254    }
255
256    /// Filter by scope type (module, function, class, struct, method, block, etc.)
257    ///
258    /// This targets the `scope.type` compound field for nested scope filtering.
259    pub fn scope_type(value: impl Into<String>) -> Self {
260        Self::condition("scope.type", Operator::Equal, Value::String(value.into()))
261    }
262
263    /// Filter by scope name
264    pub fn scope_name(value: impl Into<String>) -> Self {
265        Self::condition("scope.name", Operator::Equal, Value::String(value.into()))
266    }
267
268    /// Filter by scope parent
269    pub fn scope_parent(value: impl Into<String>) -> Self {
270        Self::condition("scope.parent", Operator::Equal, Value::String(value.into()))
271    }
272
273    /// Filter by scope ancestor (transitive parent)
274    pub fn scope_ancestor(value: impl Into<String>) -> Self {
275        Self::condition(
276            "scope.ancestor",
277            Operator::Equal,
278            Value::String(value.into()),
279        )
280    }
281
282    // ========================================================================
283    // Generic Field Access (for plugin fields)
284    // ========================================================================
285
286    /// Access any field by name with a value
287    pub fn field(name: impl Into<String>, value: impl Into<Value>) -> Self {
288        Self::condition_value(name.into(), Operator::Equal, value.into())
289    }
290
291    /// Access any field by name with regex match (default flags)
292    pub fn field_matches(name: impl Into<String>, pattern: impl Into<String>) -> Self {
293        let regex = RegexValue {
294            pattern: pattern.into(),
295            flags: RegexFlags::default(),
296        };
297        Self::condition_value(name.into(), Operator::Regex, Value::Regex(regex))
298    }
299
300    /// Access any field by name with regex match (custom flags via closure)
301    ///
302    /// # Example
303    ///
304    /// ```ignore
305    /// // Case-insensitive field matching
306    /// QueryBuilder::field_matches_with("custom_field", "pattern.*", |rb| rb.case_insensitive())
307    /// ```
308    pub fn field_matches_with<F>(
309        name: impl Into<String>,
310        pattern: impl Into<String>,
311        configure: F,
312    ) -> Self
313    where
314        F: FnOnce(RegexBuilder) -> RegexBuilder,
315    {
316        let builder = RegexBuilder::new(pattern);
317        let configured = configure(builder);
318        Self::condition_value(
319            name.into(),
320            Operator::Regex,
321            Value::Regex(configured.into_regex_value()),
322        )
323    }
324
325    /// Numeric comparison: field > value
326    pub fn field_gt(name: impl Into<String>, value: i64) -> Self {
327        Self::condition_value(name.into(), Operator::Greater, Value::Number(value))
328    }
329
330    /// Numeric comparison: field >= value
331    pub fn field_gte(name: impl Into<String>, value: i64) -> Self {
332        Self::condition_value(name.into(), Operator::GreaterEq, Value::Number(value))
333    }
334
335    /// Numeric comparison: field < value
336    pub fn field_lt(name: impl Into<String>, value: i64) -> Self {
337        Self::condition_value(name.into(), Operator::Less, Value::Number(value))
338    }
339
340    /// Numeric comparison: field <= value
341    pub fn field_lte(name: impl Into<String>, value: i64) -> Self {
342        Self::condition_value(name.into(), Operator::LessEq, Value::Number(value))
343    }
344
345    // ========================================================================
346    // Private Helpers
347    // ========================================================================
348
349    /// Create a condition with a static field name (used by core field methods)
350    fn condition(field: &'static str, operator: Operator, value: Value) -> Self {
351        Self {
352            expr: BuilderExpr::Condition(ConditionBuilder::new_static(field, operator, value)),
353            errors: Vec::new(),
354        }
355    }
356
357    /// Create a condition with a dynamic field name (used by generic field methods)
358    fn condition_value(field: String, operator: Operator, value: Value) -> Self {
359        Self {
360            expr: BuilderExpr::Condition(ConditionBuilder::new(field, operator, value)),
361            errors: Vec::new(),
362        }
363    }
364}
365
366// ============================================================================
367// Boolean Combinators
368// ============================================================================
369
370impl QueryBuilder {
371    /// Static constructor: AND of multiple conditions
372    pub fn all(conditions: Vec<QueryBuilder>) -> Self {
373        let errors = conditions.iter().flat_map(|c| c.errors.clone()).collect();
374        Self {
375            expr: BuilderExpr::And(conditions),
376            errors,
377        }
378    }
379
380    /// Static constructor: OR of multiple conditions
381    pub fn any(conditions: Vec<QueryBuilder>) -> Self {
382        let errors = conditions.iter().flat_map(|c| c.errors.clone()).collect();
383        Self {
384            expr: BuilderExpr::Or(conditions),
385            errors,
386        }
387    }
388
389    /// Chainable: combine with AND
390    pub fn and(self, other: QueryBuilder) -> Self {
391        // Merge errors from both operands
392        let mut errors = self.errors;
393        errors.extend(other.errors.clone());
394
395        match self.expr {
396            BuilderExpr::Empty => Self {
397                expr: other.expr,
398                errors,
399            },
400            BuilderExpr::And(mut exprs) => {
401                exprs.push(other);
402                Self {
403                    expr: BuilderExpr::And(exprs),
404                    errors,
405                }
406            }
407            _ => Self {
408                expr: BuilderExpr::And(vec![
409                    Self {
410                        expr: self.expr,
411                        errors: Vec::new(),
412                    },
413                    other,
414                ]),
415                errors,
416            },
417        }
418    }
419
420    /// Chainable: combine with OR
421    pub fn or(self, other: QueryBuilder) -> Self {
422        // Merge errors from both operands
423        let mut errors = self.errors;
424        errors.extend(other.errors.clone());
425
426        match self.expr {
427            BuilderExpr::Empty => Self {
428                expr: other.expr,
429                errors,
430            },
431            BuilderExpr::Or(mut exprs) => {
432                exprs.push(other);
433                Self {
434                    expr: BuilderExpr::Or(exprs),
435                    errors,
436                }
437            }
438            _ => Self {
439                expr: BuilderExpr::Or(vec![
440                    Self {
441                        expr: self.expr,
442                        errors: Vec::new(),
443                    },
444                    other,
445                ]),
446                errors,
447            },
448        }
449    }
450
451    /// Chainable: combine with AND NOT
452    pub fn and_not(self, other: QueryBuilder) -> Self {
453        self.and(Self::negate(other))
454    }
455
456    /// Static constructor: negate expression
457    ///
458    /// Named `negate` to avoid confusion with `std::ops::Not::not`.
459    /// Use this to create `NOT <expr>` conditions.
460    pub fn negate(builder: QueryBuilder) -> Self {
461        let errors = builder.errors.clone();
462        Self {
463            expr: BuilderExpr::Not(Box::new(builder)),
464            errors,
465        }
466    }
467}
468
469// ============================================================================
470// Build Methods
471// ============================================================================
472
473impl QueryBuilder {
474    /// Build the query with default field registry validation
475    ///
476    /// # Errors
477    ///
478    /// Returns `BuildError` if:
479    /// - Unknown field names are used
480    /// - Operators are incompatible with field types
481    /// - Value types don't match field types
482    /// - Enum values are invalid
483    /// - Regex patterns are syntactically invalid
484    /// - The query is empty (no conditions)
485    pub fn build(self) -> Result<Arc<QueryAST>, BuildError> {
486        let registry = FieldRegistry::with_core_fields();
487        self.build_with_registry(&registry)
488    }
489
490    /// Build with custom field registry (for plugin fields)
491    ///
492    /// This allows validation against a registry that includes plugin-specific
493    /// fields in addition to core fields.
494    ///
495    /// # Errors
496    ///
497    /// Same as `build()`.
498    pub fn build_with_registry(
499        self,
500        registry: &FieldRegistry,
501    ) -> Result<Arc<QueryAST>, BuildError> {
502        // Report any accumulated errors
503        if !self.errors.is_empty() {
504            return Err(BuildError::Multiple(self.errors));
505        }
506
507        // Convert builder expression to AST
508        let expr = self.into_expr(registry)?;
509
510        Ok(Arc::new(QueryAST {
511            root: expr,
512            span: Span::synthetic(),
513        }))
514    }
515
516    fn into_expr(self, registry: &FieldRegistry) -> Result<Expr, BuildError> {
517        match self.expr {
518            BuilderExpr::Empty => Err(BuildError::EmptyQuery),
519            BuilderExpr::Condition(ref cond) => {
520                // Validate field, operator, value, and enum constraints
521                Self::validate_condition(cond, registry)?;
522                // Clone the condition to allow consumption by into_condition
523                Ok(Expr::Condition(cond.clone().into_condition(registry)))
524            }
525            BuilderExpr::And(exprs) => {
526                let children: Result<Vec<_>, _> =
527                    exprs.into_iter().map(|e| e.into_expr(registry)).collect();
528                Ok(Expr::And(children?))
529            }
530            BuilderExpr::Or(exprs) => {
531                let children: Result<Vec<_>, _> =
532                    exprs.into_iter().map(|e| e.into_expr(registry)).collect();
533                Ok(Expr::Or(children?))
534            }
535            BuilderExpr::Not(inner) => Ok(Expr::Not(Box::new(inner.into_expr(registry)?))),
536        }
537    }
538
539    fn validate_condition(
540        cond: &ConditionBuilder,
541        registry: &FieldRegistry,
542    ) -> Result<(), BuildError> {
543        // Get field descriptor (resolves aliases)
544        let descriptor = registry
545            .get(cond.field())
546            .ok_or_else(|| BuildError::UnknownField {
547                field: cond.field().to_string(),
548                available: registry.field_names().join(", "),
549            })?;
550
551        // Check operator is valid for field type
552        if !descriptor.supports_operator(cond.operator()) {
553            return Err(BuildError::InvalidOperator {
554                field: cond.field().to_string(),
555                operator: cond.operator().clone(),
556                field_type: format!("{:?}", descriptor.field_type),
557            });
558        }
559
560        // Check value type matches field type
561        Self::validate_value_type(cond.field(), &descriptor.field_type, cond.value())?;
562
563        // Validate regex patterns early
564        // This catches invalid patterns from convenience methods like name_matches()
565        Self::validate_regex_pattern(cond.value())?;
566
567        // Check enum constraints for applicable fields
568        Self::validate_enum_value(cond.field(), cond.value(), &descriptor.field_type)?;
569
570        Ok(())
571    }
572
573    fn validate_regex_pattern(value: &Value) -> Result<(), BuildError> {
574        if let Value::Regex(regex_value) = value {
575            // Check if pattern contains lookaround assertions (FT-C.1: Support lookaround)
576            // Aligned with validator.rs behavior to accept the same patterns.
577            let has_lookaround = regex_value.pattern.contains("(?=")
578                || regex_value.pattern.contains("(?!")
579                || regex_value.pattern.contains("(?<=")
580                || regex_value.pattern.contains("(?<!");
581
582            if has_lookaround {
583                // Use fancy-regex for lookaround support
584                fancy_regex::Regex::new(&regex_value.pattern).map_err(|e| {
585                    BuildError::InvalidFancyRegex {
586                        pattern: regex_value.pattern.clone(),
587                        error: e.to_string(),
588                    }
589                })?;
590            } else {
591                // Use standard regex for performance (validate with flags applied)
592                let mut builder = regex::RegexBuilder::new(&regex_value.pattern);
593                builder.case_insensitive(regex_value.flags.case_insensitive);
594                builder.multi_line(regex_value.flags.multiline);
595                builder.dot_matches_new_line(regex_value.flags.dot_all);
596                builder.build()?;
597            }
598        }
599        Ok(())
600    }
601
602    fn validate_enum_value(
603        field: &str,
604        value: &Value,
605        field_type: &FieldType,
606    ) -> Result<(), BuildError> {
607        // Extract enum values from the field type
608        // This ensures validation stays in sync with FieldRegistry::core_fields()
609        if let (FieldType::Enum(valid), Value::String(s)) = (field_type, value)
610            && !valid.contains(&s.as_str())
611        {
612            return Err(BuildError::InvalidEnumValue {
613                field: field.to_string(),
614                value: s.clone(),
615                valid: valid.join(", "),
616            });
617        }
618
619        Ok(())
620    }
621
622    fn validate_value_type(
623        field: &str,
624        field_type: &FieldType,
625        value: &Value,
626    ) -> Result<(), BuildError> {
627        // Match value types to field types, aligned with validator.rs behavior.
628        // Regex values are valid for String, Path, and Enum fields (e.g., kind~=/function|method/).
629        let is_valid = matches!(
630            (field_type, value),
631            (
632                FieldType::String | FieldType::Path | FieldType::Enum(_),
633                Value::String(_) | Value::Regex(_)
634            ) | (FieldType::Number, Value::Number(_))
635                | (FieldType::Bool, Value::Boolean(_)) // enum regex: kind~=/function|method/
636        );
637
638        if !is_valid {
639            return Err(BuildError::ValueTypeMismatch {
640                field: field.to_string(),
641                expected: format!("{field_type:?}"),
642                actual: value.type_name().to_string(),
643            });
644        }
645
646        Ok(())
647    }
648}
649
650impl Default for QueryBuilder {
651    fn default() -> Self {
652        Self::new()
653    }
654}
655
656// ============================================================================
657// Conversion from Value types for generic field() method
658// ============================================================================
659
660impl From<&str> for Value {
661    fn from(s: &str) -> Self {
662        Value::String(s.to_string())
663    }
664}
665
666impl From<String> for Value {
667    fn from(s: String) -> Self {
668        Value::String(s)
669    }
670}
671
672impl From<i64> for Value {
673    fn from(n: i64) -> Self {
674        Value::Number(n)
675    }
676}
677
678impl From<bool> for Value {
679    fn from(b: bool) -> Self {
680        Value::Boolean(b)
681    }
682}