Skip to main content

mentedb_query/
parser.rs

1//! Hand-written recursive descent parser for MQL.
2
3use mentedb_core::edge::EdgeType;
4use mentedb_core::error::{MenteError, MenteResult};
5use mentedb_core::memory::MemoryType;
6use uuid::Uuid;
7
8use crate::ast::*;
9use crate::lexer::{Token, TokenKind};
10use mentedb_core::types::MemoryId;
11
12/// If `cond` is a pure AND of leaf filters (a single leaf, or ANDs nesting only
13/// leaves and further ANDs), return those leaves flattened. Otherwise return
14/// `None`: the tree has an OR or NOT and must be evaluated as a tree. This keeps
15/// the common `a AND b AND c` clause on the flat-filter fast path.
16fn flatten_pure_and(cond: &Condition) -> Option<Vec<Filter>> {
17    fn collect(c: &Condition, out: &mut Vec<Filter>) -> bool {
18        match c {
19            Condition::Leaf(f) => {
20                out.push(f.clone());
21                true
22            }
23            Condition::And(children) => children.iter().all(|ch| collect(ch, out)),
24            _ => false,
25        }
26    }
27    let mut out = Vec::new();
28    if collect(cond, &mut out) {
29        Some(out)
30    } else {
31        None
32    }
33}
34
35pub struct Parser<'a> {
36    tokens: &'a [Token],
37    pos: usize,
38}
39
40impl<'a> Parser<'a> {
41    pub fn new(tokens: &'a [Token]) -> Self {
42        Self { tokens, pos: 0 }
43    }
44
45    pub fn parse(tokens: &[Token]) -> MenteResult<Statement> {
46        let mut parser = Parser::new(tokens);
47        parser.parse_statement()
48    }
49
50    fn peek(&self) -> &Token {
51        &self.tokens[self.pos.min(self.tokens.len() - 1)]
52    }
53
54    fn advance(&mut self) -> &Token {
55        let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
56        if self.pos < self.tokens.len() {
57            self.pos += 1;
58        }
59        tok
60    }
61
62    fn expect(&mut self, kind: TokenKind) -> MenteResult<&Token> {
63        let tok = self.peek();
64        if tok.kind != kind {
65            return Err(MenteError::Query(format!(
66                "expected {:?}, found {:?} ('{}') at position {}",
67                kind, tok.kind, tok.lexeme, tok.position
68            )));
69        }
70        Ok(self.advance())
71    }
72
73    fn at(&self, kind: TokenKind) -> bool {
74        self.peek().kind == kind
75    }
76
77    fn parse_statement(&mut self) -> MenteResult<Statement> {
78        match self.peek().kind {
79            TokenKind::Recall => self.parse_recall(),
80            TokenKind::Relate => self.parse_relate(),
81            TokenKind::Forget => self.parse_forget(),
82            TokenKind::Consolidate => self.parse_consolidate(),
83            TokenKind::Traverse => self.parse_traverse(),
84            _ => Err(MenteError::Query(format!(
85                "expected statement keyword, found {:?} at position {}",
86                self.peek().kind,
87                self.peek().position
88            ))),
89        }
90    }
91
92    fn parse_recall(&mut self) -> MenteResult<Statement> {
93        self.advance(); // RECALL
94
95        // Optional "memories" keyword
96        if self.at(TokenKind::Memories) {
97            self.advance();
98        }
99
100        let mut near = None;
101        let mut filters = Vec::new();
102        let mut condition: Option<Condition> = None;
103        let mut limit = None;
104        let mut order_by = None;
105
106        // NEAR [vector]
107        if self.at(TokenKind::Near) {
108            self.advance();
109            near = Some(self.parse_vector()?);
110        }
111
112        // WHERE clause. Parse the full boolean expression, then keep the common
113        // pure-AND case as a flat filter list (so the planner's index selection is
114        // unchanged); only fall back to the tree when OR, NOT, or grouping appears.
115        if self.at(TokenKind::Where) {
116            self.advance();
117            let cond = self.parse_condition()?;
118            match flatten_pure_and(&cond) {
119                Some(leaves) => filters = leaves,
120                None => condition = Some(cond),
121            }
122        }
123
124        // ORDER BY field
125        if self.at(TokenKind::OrderBy) {
126            self.advance();
127            // consume optional "BY"
128            if self.at(TokenKind::By) {
129                self.advance();
130            }
131            let field = self.parse_field()?;
132            let descending = false; // default ascending
133            order_by = Some(OrderBy { field, descending });
134        }
135
136        // LIMIT n
137        if self.at(TokenKind::Limit) {
138            self.advance();
139            let tok = self.advance();
140            let n: usize = tok
141                .lexeme
142                .parse()
143                .map_err(|_| MenteError::Query(format!("invalid limit value: {}", tok.lexeme)))?;
144            limit = Some(n);
145        }
146
147        // AS OF <timestamp> — point-in-time temporal filter. Added as a ValidAt
148        // filter so it flows through the same pipeline as WHERE clauses.
149        if self.at(TokenKind::As) {
150            self.advance();
151            self.expect(TokenKind::Of)?;
152            let tok = self.advance();
153            let t: i64 = tok.lexeme.parse().map_err(|_| {
154                MenteError::Query(format!("invalid AS OF timestamp: {}", tok.lexeme))
155            })?;
156            let valid_at = Filter {
157                field: Field::ValidAt,
158                op: Operator::Eq,
159                value: Value::Integer(t),
160            };
161            // AS OF must AND with whatever WHERE produced. In the tree case, wrap
162            // it in; otherwise it is just another top-level AND leaf.
163            match condition.take() {
164                Some(c) => {
165                    condition = Some(Condition::And(vec![c, Condition::Leaf(valid_at)]));
166                }
167                None => filters.push(valid_at),
168            }
169        }
170
171        Ok(Statement::Recall(RecallStatement {
172            filters,
173            condition,
174            near,
175            limit,
176            order_by,
177        }))
178    }
179
180    fn parse_relate(&mut self) -> MenteResult<Statement> {
181        self.advance(); // RELATE
182
183        let source = self.parse_uuid()?;
184        self.expect(TokenKind::Arrow)?;
185        let target = self.parse_uuid()?;
186        self.expect(TokenKind::As)?;
187        let edge_type = self.parse_edge_type()?;
188
189        let mut weight = None;
190        if self.at(TokenKind::With) {
191            self.advance();
192            // expect "weight = <float>"
193            self.expect(TokenKind::Identifier)?; // "weight"
194            self.expect(TokenKind::Eq)?;
195            let tok = self.advance();
196            let w: f32 = tok
197                .lexeme
198                .parse()
199                .map_err(|_| MenteError::Query(format!("invalid weight value: {}", tok.lexeme)))?;
200            weight = Some(w);
201        }
202
203        Ok(Statement::Relate(RelateStatement {
204            source,
205            target,
206            edge_type,
207            weight,
208        }))
209    }
210
211    fn parse_forget(&mut self) -> MenteResult<Statement> {
212        self.advance(); // FORGET
213        let target = self.parse_uuid()?;
214        Ok(Statement::Forget(ForgetStatement { target }))
215    }
216
217    fn parse_consolidate(&mut self) -> MenteResult<Statement> {
218        self.advance(); // CONSOLIDATE
219        let mut filters = Vec::new();
220        if self.at(TokenKind::Where) {
221            self.advance();
222            filters = self.parse_filters()?;
223        }
224        Ok(Statement::Consolidate(ConsolidateStatement { filters }))
225    }
226
227    fn parse_traverse(&mut self) -> MenteResult<Statement> {
228        self.advance(); // TRAVERSE
229        let start = self.parse_uuid()?;
230
231        self.expect(TokenKind::Depth)?;
232        let tok = self.advance();
233        let depth: usize = tok
234            .lexeme
235            .parse()
236            .map_err(|_| MenteError::Query(format!("invalid depth value: {}", tok.lexeme)))?;
237
238        let mut edge_filter = None;
239        if self.at(TokenKind::Where) {
240            self.advance();
241            // edge_type = <type>
242            self.expect(TokenKind::EdgeType)?;
243            self.expect(TokenKind::Eq)?;
244            let et = self.parse_edge_type()?;
245            edge_filter = Some(vec![et]);
246        }
247
248        Ok(Statement::Traverse(TraverseStatement {
249            start,
250            depth,
251            edge_filter,
252        }))
253    }
254
255    fn parse_filters(&mut self) -> MenteResult<Vec<Filter>> {
256        let mut filters = vec![self.parse_filter()?];
257        while self.at(TokenKind::And) {
258            self.advance();
259            filters.push(self.parse_filter()?);
260        }
261        Ok(filters)
262    }
263
264    /// Parse a boolean WHERE expression with standard precedence:
265    /// OR binds loosest, then AND, then NOT, then a parenthesized group or a leaf
266    /// comparison. `a AND b OR c` parses as `(a AND b) OR c`.
267    fn parse_condition(&mut self) -> MenteResult<Condition> {
268        let mut node = self.parse_and_condition()?;
269        while self.at(TokenKind::Or) {
270            self.advance();
271            let rhs = self.parse_and_condition()?;
272            node = match node {
273                Condition::Or(mut v) => {
274                    v.push(rhs);
275                    Condition::Or(v)
276                }
277                other => Condition::Or(vec![other, rhs]),
278            };
279        }
280        Ok(node)
281    }
282
283    fn parse_and_condition(&mut self) -> MenteResult<Condition> {
284        let mut node = self.parse_not_condition()?;
285        while self.at(TokenKind::And) {
286            self.advance();
287            let rhs = self.parse_not_condition()?;
288            node = match node {
289                Condition::And(mut v) => {
290                    v.push(rhs);
291                    Condition::And(v)
292                }
293                other => Condition::And(vec![other, rhs]),
294            };
295        }
296        Ok(node)
297    }
298
299    fn parse_not_condition(&mut self) -> MenteResult<Condition> {
300        if self.at(TokenKind::Not) {
301            self.advance();
302            let inner = self.parse_not_condition()?;
303            return Ok(Condition::Not(Box::new(inner)));
304        }
305        self.parse_primary_condition()
306    }
307
308    fn parse_primary_condition(&mut self) -> MenteResult<Condition> {
309        if self.at(TokenKind::LParen) {
310            self.advance();
311            let inner = self.parse_condition()?;
312            self.expect(TokenKind::RParen)?;
313            return Ok(inner);
314        }
315        Ok(Condition::Leaf(self.parse_filter()?))
316    }
317
318    fn parse_filter(&mut self) -> MenteResult<Filter> {
319        let field = self.parse_field()?;
320        let op = self.parse_operator()?;
321        let value = if op == Operator::In {
322            self.parse_list_value(&field)?
323        } else {
324            self.parse_value(&field)?
325        };
326        Ok(Filter { field, op, value })
327    }
328
329    fn parse_field(&mut self) -> MenteResult<Field> {
330        let tok = self.advance();
331        match tok.kind {
332            TokenKind::Identifier if tok.lexeme.eq_ignore_ascii_case("content") => {
333                Ok(Field::Content)
334            }
335            TokenKind::Type => Ok(Field::Type),
336            TokenKind::Tag => Ok(Field::Tag),
337            TokenKind::Agent => Ok(Field::Agent),
338            TokenKind::Space => Ok(Field::Space),
339            TokenKind::Salience => Ok(Field::Salience),
340            TokenKind::Confidence => Ok(Field::Confidence),
341            TokenKind::Created => Ok(Field::Created),
342            TokenKind::Accessed => Ok(Field::Accessed),
343            _ => Err(MenteError::Query(format!(
344                "expected field name, found '{}' at position {}",
345                tok.lexeme, tok.position
346            ))),
347        }
348    }
349
350    fn parse_operator(&mut self) -> MenteResult<Operator> {
351        let tok = self.advance();
352        match tok.kind {
353            TokenKind::Eq => Ok(Operator::Eq),
354            TokenKind::Neq => Ok(Operator::Neq),
355            TokenKind::Gt => Ok(Operator::Gt),
356            TokenKind::Lt => Ok(Operator::Lt),
357            TokenKind::Gte => Ok(Operator::Gte),
358            TokenKind::Lte => Ok(Operator::Lte),
359            TokenKind::SimilarTo => Ok(Operator::SimilarTo),
360            TokenKind::In => Ok(Operator::In),
361            TokenKind::Contains => Ok(Operator::Contains),
362            _ => Err(MenteError::Query(format!(
363                "expected operator, found '{}' at position {}",
364                tok.lexeme, tok.position
365            ))),
366        }
367    }
368
369    /// Parse a bracketed, comma-separated list of scalar values for `IN`.
370    fn parse_list_value(&mut self, field: &Field) -> MenteResult<Value> {
371        self.expect(TokenKind::LBracket)?;
372        let mut items = Vec::new();
373        if !self.at(TokenKind::RBracket) {
374            loop {
375                items.push(self.parse_value(field)?);
376                if self.at(TokenKind::Comma) {
377                    self.advance();
378                } else {
379                    break;
380                }
381            }
382        }
383        self.expect(TokenKind::RBracket)?;
384        Ok(Value::List(items))
385    }
386
387    fn parse_value(&mut self, field: &Field) -> MenteResult<Value> {
388        // For Type field, parse as MemoryType
389        if *field == Field::Type {
390            return self.parse_memory_type_value();
391        }
392
393        let tok = self.advance();
394        match tok.kind {
395            TokenKind::StringLit => {
396                // Strip surrounding quotes
397                let inner = tok.lexeme[1..tok.lexeme.len() - 1].to_string();
398                // Check if this looks like a UUID inside quotes
399                if let Ok(uuid) = inner.parse::<MemoryId>() {
400                    return Ok(Value::Uuid(uuid.into()));
401                }
402                Ok(Value::Text(inner))
403            }
404            TokenKind::IntegerLit => {
405                let n: i64 = tok
406                    .lexeme
407                    .parse()
408                    .map_err(|_| MenteError::Query(format!("invalid integer: {}", tok.lexeme)))?;
409                Ok(Value::Integer(n))
410            }
411            TokenKind::FloatLit => {
412                let n: f64 = tok
413                    .lexeme
414                    .parse()
415                    .map_err(|_| MenteError::Query(format!("invalid float: {}", tok.lexeme)))?;
416                Ok(Value::Number(n))
417            }
418            TokenKind::UuidLit => {
419                let uuid: Uuid = tok
420                    .lexeme
421                    .parse()
422                    .map_err(|_| MenteError::Query(format!("invalid UUID: {}", tok.lexeme)))?;
423                Ok(Value::Uuid(uuid))
424            }
425            TokenKind::Identifier => {
426                let lower = tok.lexeme.to_lowercase();
427                match lower.as_str() {
428                    "true" => Ok(Value::Bool(true)),
429                    "false" => Ok(Value::Bool(false)),
430                    _ => Ok(Value::Text(tok.lexeme.clone())),
431                }
432            }
433            TokenKind::LBracket => {
434                // put back and parse as vector
435                self.pos -= 1;
436                let v = self.parse_vector()?;
437                Ok(Value::Vector(v))
438            }
439            _ => Err(MenteError::Query(format!(
440                "expected value, found '{}' at position {}",
441                tok.lexeme, tok.position
442            ))),
443        }
444    }
445
446    fn parse_memory_type_value(&mut self) -> MenteResult<Value> {
447        let tok = self.advance();
448        let name = match tok.kind {
449            TokenKind::Identifier | TokenKind::StringLit => {
450                if tok.kind == TokenKind::StringLit {
451                    tok.lexeme[1..tok.lexeme.len() - 1].to_string()
452                } else {
453                    tok.lexeme.clone()
454                }
455            }
456            _ => {
457                return Err(MenteError::Query(format!(
458                    "expected memory type, found '{}' at position {}",
459                    tok.lexeme, tok.position
460                )));
461            }
462        };
463
464        let mt = match name.to_lowercase().as_str() {
465            "episodic" => MemoryType::Episodic,
466            "semantic" => MemoryType::Semantic,
467            "procedural" => MemoryType::Procedural,
468            "antipattern" | "anti_pattern" => MemoryType::AntiPattern,
469            "reasoning" => MemoryType::Reasoning,
470            "correction" => MemoryType::Correction,
471            _ => {
472                return Err(MenteError::Query(format!("unknown memory type: {}", name)));
473            }
474        };
475        Ok(Value::MemoryType(mt))
476    }
477
478    fn parse_edge_type(&mut self) -> MenteResult<EdgeType> {
479        let tok = self.advance();
480        let name = match tok.kind {
481            TokenKind::Identifier | TokenKind::StringLit => {
482                if tok.kind == TokenKind::StringLit {
483                    tok.lexeme[1..tok.lexeme.len() - 1].to_string()
484                } else {
485                    tok.lexeme.clone()
486                }
487            }
488            _ => {
489                return Err(MenteError::Query(format!(
490                    "expected edge type, found '{}' at position {}",
491                    tok.lexeme, tok.position
492                )));
493            }
494        };
495
496        match name.to_lowercase().as_str() {
497            "caused" => Ok(EdgeType::Caused),
498            "before" => Ok(EdgeType::Before),
499            "related" => Ok(EdgeType::Related),
500            "contradicts" => Ok(EdgeType::Contradicts),
501            "supports" => Ok(EdgeType::Supports),
502            "supersedes" => Ok(EdgeType::Supersedes),
503            "derived" => Ok(EdgeType::Derived),
504            "partof" | "part_of" => Ok(EdgeType::PartOf),
505            _ => Err(MenteError::Query(format!("unknown edge type: {}", name))),
506        }
507    }
508
509    fn parse_uuid(&mut self) -> MenteResult<MemoryId> {
510        let tok = self.advance();
511        match tok.kind {
512            TokenKind::UuidLit => tok
513                .lexeme
514                .parse()
515                .map_err(|_| MenteError::Query(format!("invalid UUID: {}", tok.lexeme))),
516            TokenKind::StringLit => {
517                let inner = &tok.lexeme[1..tok.lexeme.len() - 1];
518                inner.parse().map_err(|_| {
519                    MenteError::Query(format!("invalid UUID in string: {}", tok.lexeme))
520                })
521            }
522            _ => Err(MenteError::Query(format!(
523                "expected UUID, found '{}' at position {}",
524                tok.lexeme, tok.position
525            ))),
526        }
527    }
528
529    fn parse_vector(&mut self) -> MenteResult<Vec<f32>> {
530        self.expect(TokenKind::LBracket)?;
531        let mut values = Vec::new();
532        if !self.at(TokenKind::RBracket) {
533            let tok = self.advance();
534            let v: f32 = tok.lexeme.parse().map_err(|_| {
535                MenteError::Query(format!("invalid float in vector: {}", tok.lexeme))
536            })?;
537            values.push(v);
538            while self.at(TokenKind::Comma) {
539                self.advance();
540                let tok = self.advance();
541                let v: f32 = tok.lexeme.parse().map_err(|_| {
542                    MenteError::Query(format!("invalid float in vector: {}", tok.lexeme))
543                })?;
544                values.push(v);
545            }
546        }
547        self.expect(TokenKind::RBracket)?;
548        Ok(values)
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555    use crate::lexer::tokenize;
556
557    #[test]
558    fn test_parse_recall_with_type_filter() {
559        let tokens = tokenize("RECALL memories WHERE type = episodic LIMIT 5").unwrap();
560        let stmt = Parser::parse(&tokens).unwrap();
561        match stmt {
562            Statement::Recall(r) => {
563                assert_eq!(r.filters.len(), 1);
564                assert_eq!(r.filters[0].field, Field::Type);
565                assert_eq!(r.filters[0].value, Value::MemoryType(MemoryType::Episodic));
566                assert_eq!(r.limit, Some(5));
567            }
568            _ => panic!("expected Recall"),
569        }
570    }
571
572    #[test]
573    fn test_pure_and_stays_on_flat_fast_path() {
574        // A pure AND clause must flatten to `filters` with `condition == None`, so
575        // the planner keeps its leaf-based index optimizations.
576        let tokens = tokenize("RECALL WHERE type = semantic AND tag = \"x\" LIMIT 5").unwrap();
577        match Parser::parse(&tokens).unwrap() {
578            Statement::Recall(r) => {
579                assert_eq!(r.filters.len(), 2, "both leaves flattened");
580                assert!(r.condition.is_none(), "pure AND must not build a tree");
581            }
582            _ => panic!("expected Recall"),
583        }
584    }
585
586    #[test]
587    fn test_or_builds_condition_tree() {
588        let tokens = tokenize("RECALL WHERE type = semantic OR type = procedural LIMIT 5").unwrap();
589        match Parser::parse(&tokens).unwrap() {
590            Statement::Recall(r) => {
591                assert!(r.filters.is_empty(), "OR clause is carried by the tree");
592                match r.condition {
593                    Some(Condition::Or(branches)) => assert_eq!(branches.len(), 2),
594                    other => panic!("expected Or condition, got {other:?}"),
595                }
596            }
597            _ => panic!("expected Recall"),
598        }
599    }
600
601    #[test]
602    fn test_grouping_and_not_precedence() {
603        // (a OR b) AND NOT c  =>  And[ Or[a, b], Not(c) ]
604        let tokens = tokenize(
605            "RECALL WHERE (type = semantic OR type = procedural) AND NOT tag = \"x\" LIMIT 5",
606        )
607        .unwrap();
608        match Parser::parse(&tokens).unwrap() {
609            Statement::Recall(r) => match r.condition {
610                Some(Condition::And(parts)) => {
611                    assert_eq!(parts.len(), 2);
612                    assert!(matches!(parts[0], Condition::Or(_)), "left is the OR group");
613                    assert!(matches!(parts[1], Condition::Not(_)), "right is the NOT");
614                }
615                other => panic!("expected And condition, got {other:?}"),
616            },
617            _ => panic!("expected Recall"),
618        }
619    }
620
621    #[test]
622    fn test_parse_in_operator() {
623        let tokens =
624            tokenize("RECALL memories WHERE type IN [episodic, semantic] LIMIT 5").unwrap();
625        match Parser::parse(&tokens).unwrap() {
626            Statement::Recall(r) => {
627                assert_eq!(r.filters.len(), 1);
628                assert_eq!(r.filters[0].op, Operator::In);
629                assert_eq!(
630                    r.filters[0].value,
631                    Value::List(vec![
632                        Value::MemoryType(MemoryType::Episodic),
633                        Value::MemoryType(MemoryType::Semantic),
634                    ])
635                );
636            }
637            _ => panic!("expected Recall"),
638        }
639    }
640
641    #[test]
642    fn test_parse_in_operator_text_list() {
643        let tokens = tokenize("RECALL memories WHERE tag IN [\"work\", \"home\"]").unwrap();
644        match Parser::parse(&tokens).unwrap() {
645            Statement::Recall(r) => {
646                assert_eq!(r.filters[0].field, Field::Tag);
647                assert_eq!(r.filters[0].op, Operator::In);
648                assert_eq!(
649                    r.filters[0].value,
650                    Value::List(vec![Value::Text("work".into()), Value::Text("home".into()),])
651                );
652            }
653            _ => panic!("expected Recall"),
654        }
655    }
656
657    #[test]
658    fn test_parse_contains_operator() {
659        let tokens = tokenize("RECALL memories WHERE content CONTAINS \"coffee\"").unwrap();
660        match Parser::parse(&tokens).unwrap() {
661            Statement::Recall(r) => {
662                assert_eq!(r.filters[0].field, Field::Content);
663                assert_eq!(r.filters[0].op, Operator::Contains);
664                assert_eq!(r.filters[0].value, Value::Text("coffee".into()));
665            }
666            _ => panic!("expected Recall"),
667        }
668    }
669
670    #[test]
671    fn test_parse_recall_as_of() {
672        // AS OF <t> lowers to a ValidAt filter carrying the timestamp, on top of
673        // any WHERE filters, and coexists with LIMIT.
674        let tokens =
675            tokenize("RECALL memories WHERE type = semantic LIMIT 5 AS OF 1700000000").unwrap();
676        let stmt = Parser::parse(&tokens).unwrap();
677        match stmt {
678            Statement::Recall(r) => {
679                assert_eq!(r.limit, Some(5));
680                assert_eq!(r.filters.len(), 2);
681                let valid_at = r
682                    .filters
683                    .iter()
684                    .find(|f| f.field == Field::ValidAt)
685                    .expect("expected a ValidAt filter from AS OF");
686                assert_eq!(valid_at.value, Value::Integer(1_700_000_000));
687            }
688            _ => panic!("expected Recall"),
689        }
690    }
691
692    #[test]
693    fn test_parse_recall_as_of_without_where() {
694        let tokens = tokenize("RECALL memories AS OF 42").unwrap();
695        let stmt = Parser::parse(&tokens).unwrap();
696        match stmt {
697            Statement::Recall(r) => {
698                assert_eq!(r.filters.len(), 1);
699                assert_eq!(r.filters[0].field, Field::ValidAt);
700                assert_eq!(r.filters[0].value, Value::Integer(42));
701            }
702            _ => panic!("expected Recall"),
703        }
704    }
705
706    #[test]
707    fn test_parse_recall_similar_to() {
708        let tokens =
709            tokenize(r#"RECALL memories WHERE content ~> "database migration" LIMIT 10"#).unwrap();
710        let stmt = Parser::parse(&tokens).unwrap();
711        match stmt {
712            Statement::Recall(r) => {
713                assert_eq!(r.filters.len(), 1);
714                assert_eq!(r.filters[0].op, Operator::SimilarTo);
715                assert_eq!(r.limit, Some(10));
716            }
717            _ => panic!("expected Recall"),
718        }
719    }
720
721    #[test]
722    fn test_parse_recall_near() {
723        let tokens = tokenize("RECALL memories NEAR [0.1, 0.2, 0.3] LIMIT 10").unwrap();
724        let stmt = Parser::parse(&tokens).unwrap();
725        match stmt {
726            Statement::Recall(r) => {
727                assert_eq!(r.near, Some(vec![0.1, 0.2, 0.3]));
728                assert_eq!(r.limit, Some(10));
729            }
730            _ => panic!("expected Recall"),
731        }
732    }
733
734    #[test]
735    fn test_parse_relate() {
736        let tokens = tokenize(
737            "RELATE 550e8400-e29b-41d4-a716-446655440000 -> 660e8400-e29b-41d4-a716-446655440000 AS caused WITH weight = 0.9"
738        ).unwrap();
739        let stmt = Parser::parse(&tokens).unwrap();
740        match stmt {
741            Statement::Relate(r) => {
742                assert_eq!(r.edge_type, EdgeType::Caused);
743                assert_eq!(r.weight, Some(0.9));
744            }
745            _ => panic!("expected Relate"),
746        }
747    }
748
749    #[test]
750    fn test_parse_forget() {
751        let tokens = tokenize("FORGET 550e8400-e29b-41d4-a716-446655440000").unwrap();
752        let stmt = Parser::parse(&tokens).unwrap();
753        match stmt {
754            Statement::Forget(f) => {
755                assert_eq!(
756                    f.target,
757                    "550e8400-e29b-41d4-a716-446655440000"
758                        .parse::<MemoryId>()
759                        .unwrap()
760                );
761            }
762            _ => panic!("expected Forget"),
763        }
764    }
765
766    #[test]
767    fn test_parse_consolidate() {
768        let tokens =
769            tokenize(r#"CONSOLIDATE WHERE type = episodic AND accessed < "2024-01-01""#).unwrap();
770        let stmt = Parser::parse(&tokens).unwrap();
771        match stmt {
772            Statement::Consolidate(c) => {
773                assert_eq!(c.filters.len(), 2);
774            }
775            _ => panic!("expected Consolidate"),
776        }
777    }
778
779    #[test]
780    fn test_parse_traverse() {
781        let tokens = tokenize(
782            "TRAVERSE 550e8400-e29b-41d4-a716-446655440000 DEPTH 3 WHERE edge_type = caused",
783        )
784        .unwrap();
785        let stmt = Parser::parse(&tokens).unwrap();
786        match stmt {
787            Statement::Traverse(t) => {
788                assert_eq!(t.depth, 3);
789                assert_eq!(t.edge_filter, Some(vec![EdgeType::Caused]));
790            }
791            _ => panic!("expected Traverse"),
792        }
793    }
794}