ruvector_filter/
lib.rs

1#![recursion_limit = "2048"]
2
3//! # rUvector Filter
4//!
5//! Advanced payload indexing and filtering for rUvector.
6//!
7//! This crate provides:
8//! - Flexible filter expressions (equality, range, geo, text, logical operators)
9//! - Efficient payload indexing (integer, float, keyword, boolean, geo, text)
10//! - Fast filter evaluation using indices
11//! - Support for complex queries with AND/OR/NOT
12//!
13//! ## Examples
14//!
15//! ### Creating and Using Filters
16//!
17//! ```rust
18//! use ruvector_filter::{FilterExpression, PayloadIndexManager, FilterEvaluator, IndexType};
19//! use serde_json::json;
20//!
21//! // Create index manager
22//! let mut manager = PayloadIndexManager::new();
23//! manager.create_index("status", IndexType::Keyword).unwrap();
24//! manager.create_index("age", IndexType::Integer).unwrap();
25//!
26//! // Index some payloads
27//! manager.index_payload("v1", &json!({"status": "active", "age": 25})).unwrap();
28//! manager.index_payload("v2", &json!({"status": "active", "age": 30})).unwrap();
29//! manager.index_payload("v3", &json!({"status": "inactive", "age": 25})).unwrap();
30//!
31//! // Create filter
32//! let filter = FilterExpression::and(vec![
33//!     FilterExpression::eq("status", json!("active")),
34//!     FilterExpression::gte("age", json!(25)),
35//! ]);
36//!
37//! // Evaluate filter
38//! let evaluator = FilterEvaluator::new(&manager);
39//! let results = evaluator.evaluate(&filter).unwrap();
40//! assert_eq!(results.len(), 2);
41//! ```
42//!
43//! ### Geo Filtering
44//!
45//! ```rust
46//! use ruvector_filter::{FilterExpression, PayloadIndexManager, FilterEvaluator, IndexType};
47//! use serde_json::json;
48//!
49//! let mut manager = PayloadIndexManager::new();
50//! manager.create_index("location", IndexType::Geo).unwrap();
51//!
52//! manager.index_payload("v1", &json!({
53//!     "location": {"lat": 40.7128, "lon": -74.0060}
54//! })).unwrap();
55//!
56//! // Find all points within 1000m of a location
57//! let filter = FilterExpression::geo_radius("location", 40.7128, -74.0060, 1000.0);
58//! let evaluator = FilterEvaluator::new(&manager);
59//! let results = evaluator.evaluate(&filter).unwrap();
60//! ```
61
62pub mod error;
63pub mod evaluator;
64pub mod expression;
65pub mod index;
66
67// Re-export main types
68pub use error::{FilterError, Result};
69pub use evaluator::FilterEvaluator;
70pub use expression::FilterExpression;
71pub use index::{IndexType, PayloadIndex, PayloadIndexManager};
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use serde_json::json;
77
78    #[test]
79    fn test_full_workflow() {
80        // Create index manager
81        let mut manager = PayloadIndexManager::new();
82        manager.create_index("status", IndexType::Keyword).unwrap();
83        manager.create_index("age", IndexType::Integer).unwrap();
84        manager.create_index("score", IndexType::Float).unwrap();
85
86        // Index payloads
87        manager
88            .index_payload(
89                "v1",
90                &json!({
91                    "status": "active",
92                    "age": 25,
93                    "score": 0.9
94                }),
95            )
96            .unwrap();
97
98        manager
99            .index_payload(
100                "v2",
101                &json!({
102                    "status": "active",
103                    "age": 30,
104                    "score": 0.85
105                }),
106            )
107            .unwrap();
108
109        manager
110            .index_payload(
111                "v3",
112                &json!({
113                    "status": "inactive",
114                    "age": 25,
115                    "score": 0.7
116                }),
117            )
118            .unwrap();
119
120        // Create complex filter
121        let filter = FilterExpression::and(vec![
122            FilterExpression::eq("status", json!("active")),
123            FilterExpression::or(vec![
124                FilterExpression::gte("age", json!(30)),
125                FilterExpression::gte("score", json!(0.9)),
126            ]),
127        ]);
128
129        // Evaluate
130        let evaluator = FilterEvaluator::new(&manager);
131        let results = evaluator.evaluate(&filter).unwrap();
132
133        // Should match v1 (age=25, score=0.9) and v2 (age=30, score=0.85)
134        assert_eq!(results.len(), 2);
135        assert!(results.contains("v1"));
136        assert!(results.contains("v2"));
137    }
138
139    #[test]
140    fn test_text_matching() {
141        let mut manager = PayloadIndexManager::new();
142        manager
143            .create_index("description", IndexType::Text)
144            .unwrap();
145
146        manager
147            .index_payload(
148                "v1",
149                &json!({
150                    "description": "The quick brown fox"
151                }),
152            )
153            .unwrap();
154
155        manager
156            .index_payload(
157                "v2",
158                &json!({
159                    "description": "The lazy dog"
160                }),
161            )
162            .unwrap();
163
164        let evaluator = FilterEvaluator::new(&manager);
165        let filter = FilterExpression::match_text("description", "quick");
166        let results = evaluator.evaluate(&filter).unwrap();
167
168        assert_eq!(results.len(), 1);
169        assert!(results.contains("v1"));
170    }
171
172    #[test]
173    fn test_not_filter() {
174        let mut manager = PayloadIndexManager::new();
175        manager.create_index("status", IndexType::Keyword).unwrap();
176
177        manager
178            .index_payload("v1", &json!({"status": "active"}))
179            .unwrap();
180        manager
181            .index_payload("v2", &json!({"status": "inactive"}))
182            .unwrap();
183
184        let evaluator = FilterEvaluator::new(&manager);
185        let filter = FilterExpression::not(FilterExpression::eq("status", json!("active")));
186        let results = evaluator.evaluate(&filter).unwrap();
187
188        assert_eq!(results.len(), 1);
189        assert!(results.contains("v2"));
190    }
191
192    #[test]
193    fn test_in_filter() {
194        let mut manager = PayloadIndexManager::new();
195        manager.create_index("status", IndexType::Keyword).unwrap();
196
197        manager
198            .index_payload("v1", &json!({"status": "active"}))
199            .unwrap();
200        manager
201            .index_payload("v2", &json!({"status": "pending"}))
202            .unwrap();
203        manager
204            .index_payload("v3", &json!({"status": "inactive"}))
205            .unwrap();
206
207        let evaluator = FilterEvaluator::new(&manager);
208        let filter = FilterExpression::in_values("status", vec![json!("active"), json!("pending")]);
209        let results = evaluator.evaluate(&filter).unwrap();
210
211        assert_eq!(results.len(), 2);
212        assert!(results.contains("v1"));
213        assert!(results.contains("v2"));
214    }
215}