Skip to main content

fcb_core/static_btree/query/
http.rs

1use std::collections::HashMap;
2use std::marker::PhantomData;
3
4use crate::static_btree::error::{Error, Result};
5use crate::static_btree::key::{Key, KeyType};
6use crate::static_btree::query::types::{Operator, QueryCondition};
7use crate::static_btree::stree::http::HttpSearchResultItem;
8use crate::static_btree::stree::Stree;
9use async_trait::async_trait;
10use http_range_client::{AsyncBufferedHttpRangeClient, AsyncHttpRangeClient};
11
12/// HTTP-based index for remote access
13#[derive(Debug, Clone)]
14pub struct HttpIndex<K: Key> {
15    /// total number of items in the tree
16    num_items: usize,
17    /// branching factor of the B+tree
18    branching_factor: u16,
19    /// byte offset where the index begins
20    index_begin: usize,
21    /// byte offset where the feature data begins
22    feature_begin: usize,
23    /// threshold for combining HTTP requests to reduce roundtrips
24    combine_request_threshold: usize,
25    _marker: PhantomData<K>,
26}
27
28impl<K: Key> HttpIndex<K> {
29    /// Create a new HTTP index descriptor with all necessary metadata
30    pub fn new(
31        num_items: usize,
32        branching_factor: u16,
33        index_begin: usize,
34        feature_begin: usize,
35        combine_request_threshold: usize,
36    ) -> Self {
37        Self {
38            num_items,
39            branching_factor,
40            index_begin,
41            feature_begin,
42            combine_request_threshold,
43            _marker: PhantomData,
44        }
45    }
46
47    /// Find exact matches for a key via HTTP
48    pub async fn find_exact<T: AsyncHttpRangeClient>(
49        &self,
50        client: &mut AsyncBufferedHttpRangeClient<T>,
51        key: K,
52    ) -> Result<Vec<HttpSearchResultItem>> {
53        let items: Vec<HttpSearchResultItem> = Stree::http_stream_find_exact(
54            client,
55            self.index_begin,
56            self.feature_begin,
57            self.num_items,
58            self.branching_factor,
59            key.clone(),
60            self.combine_request_threshold,
61        )
62        .await?;
63
64        Ok(items)
65    }
66
67    /// Find all items in [start..end] via HTTP. At least one bound is required.
68    pub async fn find_range<T: AsyncHttpRangeClient>(
69        &self,
70        client: &mut AsyncBufferedHttpRangeClient<T>,
71        start: Option<K>,
72        end: Option<K>,
73    ) -> Result<Vec<HttpSearchResultItem>> {
74        let (lower, upper) = match (start, end) {
75            (Some(lo), Some(hi)) => (lo, hi),
76            (Some(lo), None) => (lo, K::max_value()),
77            (None, Some(hi)) => (K::min_value(), hi),
78            (None, None) => {
79                return Err(Error::QueryError(
80                    "find_range requires at least one bound".to_string(),
81                ));
82            }
83        };
84
85        let items: Vec<HttpSearchResultItem> = Stree::http_stream_find_range(
86            client,
87            self.index_begin,
88            self.feature_begin,
89            self.num_items,
90            self.branching_factor,
91            lower.clone(),
92            upper.clone(),
93            self.combine_request_threshold,
94        )
95        .await?;
96
97        Ok(items)
98    }
99
100    /// Find all items in the range via HTTP, with each bound independently
101    /// strict (exclusive) or inclusive. A `None` bound is the type's min/max
102    /// sentinel and is never strict.
103    ///
104    /// `Gt`/`Lt`/`Ne` lower to this instead of subtracting `find_exact` from
105    /// an inclusive range: the subtraction removes feature offsets, and one
106    /// feature can be indexed under several keys, so it deletes features that
107    /// match through a different key.
108    pub async fn find_range_strict<T: AsyncHttpRangeClient>(
109        &self,
110        client: &mut AsyncBufferedHttpRangeClient<T>,
111        start: Option<K>,
112        start_strict: bool,
113        end: Option<K>,
114        end_strict: bool,
115    ) -> Result<Vec<HttpSearchResultItem>> {
116        let lower = start.unwrap_or_else(K::min_value);
117        let upper = end.unwrap_or_else(K::max_value);
118
119        let items: Vec<HttpSearchResultItem> = Stree::http_stream_find_range_strict(
120            client,
121            self.index_begin,
122            self.feature_begin,
123            self.num_items,
124            self.branching_factor,
125            lower,
126            start_strict,
127            upper,
128            end_strict,
129            self.combine_request_threshold,
130        )
131        .await?;
132
133        Ok(items)
134    }
135}
136
137/// Trait for HTTP indices with heterogeneous key support
138#[cfg(not(target_arch = "wasm32"))]
139#[async_trait]
140pub trait TypedHttpSearchIndex<T: AsyncHttpRangeClient + Send + Sync>:
141    Send + Sync + std::fmt::Debug
142{
143    /// Execute a typed query condition over HTTP with a specific HTTP client
144    async fn execute_query_condition(
145        &self,
146        client: &mut AsyncBufferedHttpRangeClient<T>,
147        condition: &QueryCondition,
148    ) -> Result<Vec<HttpSearchResultItem>>;
149}
150
151/// Wasm-specific version that doesn't require Send + Sync
152#[cfg(target_arch = "wasm32")]
153#[async_trait(?Send)]
154pub trait TypedHttpSearchIndex<T: AsyncHttpRangeClient>: std::fmt::Debug {
155    /// Execute a typed query condition over HTTP with a specific HTTP client
156    async fn execute_query_condition(
157        &self,
158        client: &mut AsyncBufferedHttpRangeClient<T>,
159        condition: &QueryCondition,
160    ) -> Result<Vec<HttpSearchResultItem>>;
161}
162
163/// Implement the TypedHttpSearchIndex trait for each supported key type
164macro_rules! impl_typed_http_search_index {
165    ($key_type:ty, $enum_variant:path) => {
166        #[cfg(not(target_arch = "wasm32"))]
167        #[async_trait]
168        impl<T: AsyncHttpRangeClient + Send + Sync> TypedHttpSearchIndex<T>
169            for HttpIndex<$key_type>
170        {
171            async fn execute_query_condition(
172                &self,
173                client: &mut AsyncBufferedHttpRangeClient<T>,
174                condition: &QueryCondition,
175            ) -> Result<Vec<HttpSearchResultItem>> {
176                // Extract the key value from the enum variant
177                let key: $key_type = match &condition.key {
178                    $enum_variant(val) => val.clone(),
179                    _ => {
180                        return Err(Error::QueryError(format!(
181                            "key type mismatch: expected {}, got {:?}",
182                            stringify!($key_type),
183                            condition.key
184                        )))
185                    }
186                };
187
188                // Dispatch to exact or range methods
189                let results = match condition.operator {
190                    Operator::Eq => self.find_exact(client, key.clone()).await?,
191                    // Two half-open scans rather than a full scan minus the
192                    // equal set: subtraction on feature offsets is wrong when
193                    // one feature carries several values of the attribute.
194                    Operator::Ne => {
195                        let mut below = self
196                            .find_range_strict(client, None, false, Some(key.clone()), true)
197                            .await?;
198                        let above = self
199                            .find_range_strict(client, Some(key.clone()), true, None, false)
200                            .await?;
201                        below.extend(above);
202                        below
203                    }
204                    Operator::Gt => {
205                        self.find_range_strict(client, Some(key.clone()), true, None, false)
206                            .await?
207                    }
208                    Operator::Lt => {
209                        self.find_range_strict(client, None, false, Some(key.clone()), true)
210                            .await?
211                    }
212                    Operator::Ge => {
213                        self.find_range_strict(client, Some(key.clone()), false, None, false)
214                            .await?
215                    }
216                    Operator::Le => {
217                        self.find_range_strict(client, None, false, Some(key.clone()), false)
218                            .await?
219                    }
220                };
221                Ok(results)
222            }
223        }
224
225        #[cfg(target_arch = "wasm32")]
226        #[async_trait(?Send)]
227        impl<T: AsyncHttpRangeClient> TypedHttpSearchIndex<T> for HttpIndex<$key_type> {
228            async fn execute_query_condition(
229                &self,
230                client: &mut AsyncBufferedHttpRangeClient<T>,
231                condition: &QueryCondition,
232            ) -> Result<Vec<HttpSearchResultItem>> {
233                // Extract the key value from the enum variant
234                let key: $key_type = match &condition.key {
235                    $enum_variant(val) => val.clone(),
236                    _ => {
237                        return Err(Error::QueryError(format!(
238                            "key type mismatch: expected {}, got {:?}",
239                            stringify!($key_type),
240                            condition.key
241                        )))
242                    }
243                };
244
245                // Dispatch to exact or range methods
246                let results = match condition.operator {
247                    Operator::Eq => self.find_exact(client, key.clone()).await?,
248                    // Two half-open scans rather than a full scan minus the
249                    // equal set: subtraction on feature offsets is wrong when
250                    // one feature carries several values of the attribute.
251                    Operator::Ne => {
252                        let mut below = self
253                            .find_range_strict(client, None, false, Some(key.clone()), true)
254                            .await?;
255                        let above = self
256                            .find_range_strict(client, Some(key.clone()), true, None, false)
257                            .await?;
258                        below.extend(above);
259                        below
260                    }
261                    Operator::Gt => {
262                        self.find_range_strict(client, Some(key.clone()), true, None, false)
263                            .await?
264                    }
265                    Operator::Lt => {
266                        self.find_range_strict(client, None, false, Some(key.clone()), true)
267                            .await?
268                    }
269                    Operator::Ge => {
270                        self.find_range_strict(client, Some(key.clone()), false, None, false)
271                            .await?
272                    }
273                    Operator::Le => {
274                        self.find_range_strict(client, None, false, Some(key.clone()), false)
275                            .await?
276                    }
277                };
278                Ok(results)
279            }
280        }
281    };
282}
283
284impl_typed_http_search_index!(i8, KeyType::Int8);
285impl_typed_http_search_index!(u8, KeyType::UInt8);
286impl_typed_http_search_index!(i16, KeyType::Int16);
287impl_typed_http_search_index!(u16, KeyType::UInt16);
288impl_typed_http_search_index!(i32, KeyType::Int32);
289impl_typed_http_search_index!(i64, KeyType::Int64);
290impl_typed_http_search_index!(u32, KeyType::UInt32);
291impl_typed_http_search_index!(u64, KeyType::UInt64);
292impl_typed_http_search_index!(ordered_float::OrderedFloat<f32>, KeyType::Float32);
293impl_typed_http_search_index!(ordered_float::OrderedFloat<f64>, KeyType::Float64);
294impl_typed_http_search_index!(bool, KeyType::Bool);
295impl_typed_http_search_index!(chrono::DateTime<chrono::Utc>, KeyType::DateTime);
296impl_typed_http_search_index!(
297    crate::static_btree::key::FixedStringKey<20>,
298    KeyType::StringKey20
299);
300impl_typed_http_search_index!(
301    crate::static_btree::key::FixedStringKey<50>,
302    KeyType::StringKey50
303);
304impl_typed_http_search_index!(
305    crate::static_btree::key::FixedStringKey<100>,
306    KeyType::StringKey100
307);
308
309/// Container for multiple HTTP indices keyed by field name
310#[derive(Debug)]
311#[cfg(not(target_arch = "wasm32"))]
312pub struct HttpMultiIndex<T: AsyncHttpRangeClient + Send + Sync> {
313    indices: HashMap<String, Box<dyn TypedHttpSearchIndex<T>>>,
314}
315
316#[cfg(not(target_arch = "wasm32"))]
317impl<T: AsyncHttpRangeClient + Send + Sync> HttpMultiIndex<T> {
318    /// Create a new empty HTTP multi-index
319    pub fn new() -> Self {
320        Self {
321            indices: HashMap::new(),
322        }
323    }
324
325    /// Add an index for any supported key type
326    pub fn add_index<K: Key + 'static>(&mut self, field: String, index: HttpIndex<K>)
327    where
328        HttpIndex<K>: TypedHttpSearchIndex<T> + 'static,
329    {
330        self.indices.insert(field, Box::new(index));
331    }
332
333    /// Execute a multi-condition query by AND-ing all conditions
334    pub async fn query(
335        &self,
336        client: &mut AsyncBufferedHttpRangeClient<T>,
337        conditions: &[QueryCondition],
338    ) -> Result<Vec<HttpSearchResultItem>> {
339        if conditions.is_empty() {
340            return Err(Error::QueryError("query cannot be empty".to_string()));
341        }
342        let mut result_sets = Vec::with_capacity(conditions.len());
343        for cond in conditions {
344            let idx = self.indices.get(&cond.field).ok_or_else(|| {
345                Error::QueryError(format!("no index found for field '{}'", cond.field))
346            })?;
347            let items = idx.execute_query_condition(client, cond).await?;
348            result_sets.push(items);
349            if result_sets.is_empty() {
350                // no results found for this condition, return early so we don't waste time intersecting empty sets
351                return Ok(vec![]);
352            }
353        }
354        // intersect all sets
355        let mut iter = result_sets.into_iter();
356        let mut intersection = iter.next().unwrap_or_default();
357        for set in iter {
358            intersection.retain(|x| set.contains(x));
359        }
360        Ok(intersection)
361    }
362}
363
364#[cfg(not(target_arch = "wasm32"))]
365impl<T: AsyncHttpRangeClient + Send + Sync> Default for HttpMultiIndex<T> {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// Container for multiple HTTP indices keyed by field name (WASM version)
372#[derive(Debug)]
373#[cfg(target_arch = "wasm32")]
374pub struct HttpMultiIndex<T: AsyncHttpRangeClient> {
375    indices: HashMap<String, Box<dyn TypedHttpSearchIndex<T>>>,
376}
377
378#[cfg(target_arch = "wasm32")]
379impl<T: AsyncHttpRangeClient> HttpMultiIndex<T> {
380    /// Create a new empty HTTP multi-index
381    pub fn new() -> Self {
382        Self {
383            indices: HashMap::new(),
384        }
385    }
386
387    /// Add an index for any supported key type
388    pub fn add_index<K: Key + 'static>(&mut self, field: String, index: HttpIndex<K>)
389    where
390        HttpIndex<K>: TypedHttpSearchIndex<T> + 'static,
391    {
392        self.indices.insert(field, Box::new(index));
393    }
394    /// Execute a multi-condition query by AND-ing all conditions
395    pub async fn query(
396        &self,
397        client: &mut AsyncBufferedHttpRangeClient<T>,
398        conditions: &[QueryCondition],
399    ) -> Result<Vec<HttpSearchResultItem>> {
400        if conditions.is_empty() {
401            return Err(Error::QueryError("query cannot be empty".to_string()));
402        }
403        let mut result_sets = Vec::with_capacity(conditions.len());
404
405        for cond in conditions {
406            // print the field name of condition and indices names
407
408            let idx = self.indices.get(&cond.field).ok_or_else(|| {
409                Error::QueryError(format!("no index found for field '{}'", cond.field))
410            })?;
411            let items = idx.execute_query_condition(client, cond).await?;
412            result_sets.push(items);
413            if result_sets.is_empty() {
414                // no results found for this condition, return early so we don't waste time intersecting empty sets
415                return Ok(vec![]);
416            }
417        }
418        // intersect all sets
419        let mut iter = result_sets.into_iter();
420        let mut intersection = iter.next().unwrap_or_default();
421        for set in iter {
422            intersection.retain(|x| set.contains(x));
423        }
424        Ok(intersection)
425    }
426}
427
428#[cfg(target_arch = "wasm32")]
429impl<T: AsyncHttpRangeClient> Default for HttpMultiIndex<T> {
430    fn default() -> Self {
431        Self::new()
432    }
433}