Skip to main content

marisa/
query.rs

1//! Query type for trie searches.
2//!
3//! Ported from: include/marisa/query.h
4
5use std::fmt;
6
7/// Query represents a search query with a string and optional ID.
8///
9/// A query can contain either:
10/// - A string to search for (ptr + length)
11/// - An ID to reverse lookup
12#[derive(Clone)]
13pub struct Query {
14    /// Pointer to query string data (borrowed).
15    ptr: Option<*const u8>,
16    /// Length of query string.
17    length: usize,
18    /// Query ID for reverse lookup.
19    id: usize,
20}
21
22// Manual Debug implementation since raw pointers don't implement Debug
23impl fmt::Debug for Query {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.debug_struct("Query")
26            .field("ptr", &self.ptr.map(|_| "..."))
27            .field("length", &self.length)
28            .field("id", &self.id)
29            .finish()
30    }
31}
32
33impl Default for Query {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl Query {
40    /// Creates a new empty query.
41    pub fn new() -> Self {
42        Query {
43            ptr: None,
44            length: 0,
45            id: 0,
46        }
47    }
48
49    /// Returns the character at the specified index.
50    ///
51    /// # Panics
52    ///
53    /// Panics if index is out of bounds.
54    pub fn get(&self, i: usize) -> u8 {
55        assert!(i < self.length, "Index out of bounds");
56        if let Some(ptr) = self.ptr {
57            unsafe { *ptr.add(i) }
58        } else {
59            panic!("Query has no string data");
60        }
61    }
62
63    /// Sets the query from a string slice.
64    pub fn set_str(&mut self, s: &str) {
65        self.ptr = Some(s.as_ptr());
66        self.length = s.len();
67    }
68
69    /// Sets the query from a byte slice.
70    pub fn set_bytes(&mut self, bytes: &[u8]) {
71        if bytes.is_empty() {
72            self.ptr = None;
73            self.length = 0;
74        } else {
75            self.ptr = Some(bytes.as_ptr());
76            self.length = bytes.len();
77        }
78    }
79
80    /// Sets the query ID.
81    pub fn set_id(&mut self, id: usize) {
82        self.id = id;
83    }
84
85    /// Returns the query as a byte slice.
86    ///
87    /// Returns an empty slice if no string is set.
88    pub fn as_bytes(&self) -> &[u8] {
89        if let Some(ptr) = self.ptr {
90            unsafe { std::slice::from_raw_parts(ptr, self.length) }
91        } else {
92            &[]
93        }
94    }
95
96    /// Returns the query string as a str reference.
97    ///
98    /// # Panics
99    ///
100    /// Panics if the query contains invalid UTF-8.
101    pub fn as_str(&self) -> &str {
102        std::str::from_utf8(self.as_bytes()).expect("Invalid UTF-8 in query")
103    }
104
105    /// Returns a pointer to the query data.
106    pub fn ptr(&self) -> Option<*const u8> {
107        self.ptr
108    }
109
110    /// Returns the length of the query string.
111    pub fn length(&self) -> usize {
112        self.length
113    }
114
115    /// Returns the query ID.
116    pub fn id(&self) -> usize {
117        self.id
118    }
119
120    /// Clears the query to empty state.
121    pub fn clear(&mut self) {
122        *self = Query::new();
123    }
124
125    /// Swaps with another query.
126    pub fn swap(&mut self, other: &mut Query) {
127        std::mem::swap(self, other);
128    }
129}
130
131// Safety: Query only holds a pointer that must remain valid for its lifetime.
132// The user is responsible for ensuring the borrowed data outlives the Query.
133unsafe impl Send for Query {}
134unsafe impl Sync for Query {}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_query_new() {
142        let query = Query::new();
143        assert_eq!(query.length(), 0);
144        assert_eq!(query.id(), 0);
145        assert_eq!(query.as_bytes(), &[]);
146    }
147
148    #[test]
149    fn test_query_default() {
150        let query = Query::default();
151        assert_eq!(query.length(), 0);
152    }
153
154    #[test]
155    fn test_query_set_str() {
156        let s = "hello";
157        let mut query = Query::new();
158        query.set_str(s);
159
160        assert_eq!(query.length(), 5);
161        assert_eq!(query.as_str(), "hello");
162        assert_eq!(query.as_bytes(), b"hello");
163    }
164
165    #[test]
166    fn test_query_set_bytes() {
167        let bytes = b"world";
168        let mut query = Query::new();
169        query.set_bytes(bytes);
170
171        assert_eq!(query.length(), 5);
172        assert_eq!(query.as_bytes(), b"world");
173    }
174
175    #[test]
176    fn test_query_set_empty_bytes() {
177        let mut query = Query::new();
178        query.set_str("test");
179        query.set_bytes(&[]);
180
181        assert_eq!(query.length(), 0);
182        assert_eq!(query.as_bytes(), &[]);
183    }
184
185    #[test]
186    fn test_query_get() {
187        let s = "test";
188        let mut query = Query::new();
189        query.set_str(s);
190
191        assert_eq!(query.get(0), b't');
192        assert_eq!(query.get(1), b'e');
193        assert_eq!(query.get(2), b's');
194        assert_eq!(query.get(3), b't');
195    }
196
197    #[test]
198    #[should_panic(expected = "Index out of bounds")]
199    fn test_query_get_out_of_bounds() {
200        let s = "test";
201        let mut query = Query::new();
202        query.set_str(s);
203        query.get(4);
204    }
205
206    #[test]
207    fn test_query_set_id() {
208        let mut query = Query::new();
209        query.set_id(42);
210        assert_eq!(query.id(), 42);
211    }
212
213    #[test]
214    fn test_query_clear() {
215        let mut query = Query::new();
216        query.set_str("test");
217        query.set_id(10);
218
219        query.clear();
220
221        assert_eq!(query.length(), 0);
222        assert_eq!(query.id(), 0);
223        assert_eq!(query.as_bytes(), &[]);
224    }
225
226    #[test]
227    fn test_query_swap() {
228        let s1 = "hello";
229        let s2 = "world";
230
231        let mut q1 = Query::new();
232        q1.set_str(s1);
233        q1.set_id(1);
234
235        let mut q2 = Query::new();
236        q2.set_str(s2);
237        q2.set_id(2);
238
239        q1.swap(&mut q2);
240
241        assert_eq!(q1.as_str(), "world");
242        assert_eq!(q1.id(), 2);
243        assert_eq!(q2.as_str(), "hello");
244        assert_eq!(q2.id(), 1);
245    }
246
247    #[test]
248    fn test_query_with_unicode() {
249        let s = "こんにちは";
250        let mut query = Query::new();
251        query.set_str(s);
252
253        assert_eq!(query.length(), s.len());
254        assert_eq!(query.as_str(), "こんにちは");
255    }
256}