Skip to main content

rushdb_query/
property_predicate.rs

1//! Predicates over one object's own properties, evaluated without the
2//! graph around it (relationship properties): the full field-operator set
3//! of the record predicate, but no traversal blocks.
4
5use crate::document::{self, QueryDocumentError};
6use crate::{eval_pred, Expr, Json, Map, QueryError};
7
8/// Parse a property predicate that is evaluated on one object without the
9/// graph around it (relationship properties): the full operator set, but
10/// no traversal blocks.
11///
12/// # Errors
13/// Returns [`QueryDocumentError::Parse`] for a malformed predicate or a
14/// traversal block.
15pub fn parse_property_predicate(where_: &Json) -> Result<Expr, QueryDocumentError> {
16    fn traverses(expr: &Expr) -> bool {
17        match expr {
18            Expr::And(items) | Expr::Or(items) | Expr::Nor(items) => items.iter().any(traverses),
19            Expr::Xor(a, b) => traverses(a) || traverses(b),
20            Expr::Not(inner) => traverses(inner),
21            Expr::Field(_) => false,
22            Expr::Related(_) => true,
23        }
24    }
25    let expr = document::parse_where(where_)?;
26    if traverses(&expr) {
27        return Err(QueryDocumentError::Parse(
28            "a relationship predicate filters the relationship's own properties; \
29             traversal blocks are not supported"
30                .into(),
31        ));
32    }
33    Ok(expr)
34}
35
36/// Evaluate a [`parse_property_predicate`] expression on one object. `$type`
37/// tests the stored JSON shape (there is no declared type to consult).
38///
39/// # Errors
40/// Returns [`QueryError`] when evaluation fails.
41pub fn matches_properties(expr: &Expr, obj: &Map<String, Json>) -> Result<bool, QueryError> {
42    Ok(match expr {
43        Expr::And(items) => {
44            for item in items {
45                if !matches_properties(item, obj)? {
46                    return Ok(false);
47                }
48            }
49            true
50        }
51        Expr::Or(items) | Expr::Nor(items) => {
52            let mut any = false;
53            for item in items {
54                if matches_properties(item, obj)? {
55                    any = true;
56                    break;
57                }
58            }
59            any != matches!(expr, Expr::Nor(_))
60        }
61        Expr::Xor(a, b) => matches_properties(a, obj)? != matches_properties(b, obj)?,
62        Expr::Not(inner) => !matches_properties(inner, obj)?,
63        Expr::Field(pred) => eval_pred(&|_| Ok(None), pred, obj)?,
64        Expr::Related(_) => false,
65    })
66}