mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-09-12 07:36:29 +00:00
Move crates under a sub folder to clean up the code
This commit is contained in:
846
crates/milli/src/search/facet/facet_distribution.rs
Normal file
846
crates/milli/src/search/facet/facet_distribution.rs
Normal file
@ -0,0 +1,846 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::ops::ControlFlow;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use heed::types::Bytes;
|
||||
use heed::BytesDecode;
|
||||
use indexmap::IndexMap;
|
||||
use roaring::RoaringBitmap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::UserError;
|
||||
use crate::facet::FacetType;
|
||||
use crate::heed_codec::facet::{
|
||||
FacetGroupKeyCodec, FieldDocIdFacetF64Codec, FieldDocIdFacetStringCodec, OrderedF64Codec,
|
||||
};
|
||||
use crate::heed_codec::{BytesRefCodec, StrRefCodec};
|
||||
use crate::search::facet::facet_distribution_iter::{
|
||||
count_iterate_over_facet_distribution, lexicographically_iterate_over_facet_distribution,
|
||||
};
|
||||
use crate::{FieldId, Index, Result};
|
||||
|
||||
/// The default number of values by facets that will
|
||||
/// be fetched from the key-value store.
|
||||
pub const DEFAULT_VALUES_PER_FACET: usize = 100;
|
||||
|
||||
/// Threshold on the number of candidates that will make
|
||||
/// the system to choose between one algorithm or another.
|
||||
const CANDIDATES_THRESHOLD: u64 = 3000;
|
||||
|
||||
/// How should we fetch the facets?
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum OrderBy {
|
||||
/// By lexicographic order...
|
||||
#[default]
|
||||
Lexicographic,
|
||||
/// Or by number of docids in common?
|
||||
Count,
|
||||
}
|
||||
|
||||
impl Display for OrderBy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OrderBy::Lexicographic => f.write_str("alphabetically"),
|
||||
OrderBy::Count => f.write_str("by count"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FacetDistribution<'a> {
|
||||
facets: Option<HashMap<String, OrderBy>>,
|
||||
candidates: Option<RoaringBitmap>,
|
||||
max_values_per_facet: usize,
|
||||
default_order_by: OrderBy,
|
||||
rtxn: &'a heed::RoTxn<'a>,
|
||||
index: &'a Index,
|
||||
}
|
||||
|
||||
impl<'a> FacetDistribution<'a> {
|
||||
pub fn new(rtxn: &'a heed::RoTxn<'a>, index: &'a Index) -> FacetDistribution<'a> {
|
||||
FacetDistribution {
|
||||
facets: None,
|
||||
candidates: None,
|
||||
max_values_per_facet: DEFAULT_VALUES_PER_FACET,
|
||||
default_order_by: OrderBy::default(),
|
||||
rtxn,
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn facets<I: IntoIterator<Item = (A, OrderBy)>, A: AsRef<str>>(
|
||||
&mut self,
|
||||
names_ordered_by: I,
|
||||
) -> &mut Self {
|
||||
self.facets = Some(
|
||||
names_ordered_by
|
||||
.into_iter()
|
||||
.map(|(name, order_by)| (name.as_ref().to_string(), order_by))
|
||||
.collect(),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_values_per_facet(&mut self, max: usize) -> &mut Self {
|
||||
self.max_values_per_facet = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_order_by(&mut self, order_by: OrderBy) -> &mut Self {
|
||||
self.default_order_by = order_by;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn candidates(&mut self, candidates: RoaringBitmap) -> &mut Self {
|
||||
self.candidates = Some(candidates);
|
||||
self
|
||||
}
|
||||
|
||||
/// There is a small amount of candidates OR we ask for facet string values so we
|
||||
/// decide to iterate over the facet values of each one of them, one by one.
|
||||
fn facet_distribution_from_documents(
|
||||
&self,
|
||||
field_id: FieldId,
|
||||
facet_type: FacetType,
|
||||
candidates: &RoaringBitmap,
|
||||
distribution: &mut IndexMap<String, u64>,
|
||||
) -> heed::Result<()> {
|
||||
match facet_type {
|
||||
FacetType::Number => {
|
||||
let mut lexicographic_distribution = BTreeMap::new();
|
||||
let mut key_buffer: Vec<_> = field_id.to_be_bytes().to_vec();
|
||||
|
||||
let db = self.index.field_id_docid_facet_f64s;
|
||||
for docid in candidates {
|
||||
key_buffer.truncate(mem::size_of::<FieldId>());
|
||||
key_buffer.extend_from_slice(&docid.to_be_bytes());
|
||||
let iter = db
|
||||
.remap_key_type::<Bytes>()
|
||||
.prefix_iter(self.rtxn, &key_buffer)?
|
||||
.remap_key_type::<FieldDocIdFacetF64Codec>();
|
||||
|
||||
for result in iter {
|
||||
let ((_, _, value), ()) = result?;
|
||||
*lexicographic_distribution.entry(value.to_string()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
distribution.extend(
|
||||
lexicographic_distribution
|
||||
.into_iter()
|
||||
.take(self.max_values_per_facet.saturating_sub(distribution.len())),
|
||||
);
|
||||
}
|
||||
FacetType::String => {
|
||||
let mut normalized_distribution = BTreeMap::new();
|
||||
let mut key_buffer: Vec<_> = field_id.to_be_bytes().to_vec();
|
||||
|
||||
let db = self.index.field_id_docid_facet_strings;
|
||||
for docid in candidates {
|
||||
key_buffer.truncate(mem::size_of::<FieldId>());
|
||||
key_buffer.extend_from_slice(&docid.to_be_bytes());
|
||||
let iter = db
|
||||
.remap_key_type::<Bytes>()
|
||||
.prefix_iter(self.rtxn, &key_buffer)?
|
||||
.remap_key_type::<FieldDocIdFacetStringCodec>();
|
||||
|
||||
for result in iter {
|
||||
let ((_, _, normalized_value), original_value) = result?;
|
||||
let (_, count) = normalized_distribution
|
||||
.entry(normalized_value)
|
||||
.or_insert_with(|| (original_value, 0));
|
||||
*count += 1;
|
||||
|
||||
// we'd like to break here if we have enough facet values, but we are collecting them by increasing docid,
|
||||
// so higher ranked facets could be in later docids
|
||||
}
|
||||
}
|
||||
|
||||
let iter = normalized_distribution
|
||||
.into_iter()
|
||||
.take(self.max_values_per_facet.saturating_sub(distribution.len()))
|
||||
.map(|(_normalized, (original, count))| (original.to_string(), count));
|
||||
distribution.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// There is too much documents, we use the facet levels to move throught
|
||||
/// the facet values, to find the candidates and values associated.
|
||||
fn facet_numbers_distribution_from_facet_levels(
|
||||
&self,
|
||||
field_id: FieldId,
|
||||
candidates: &RoaringBitmap,
|
||||
order_by: OrderBy,
|
||||
distribution: &mut IndexMap<String, u64>,
|
||||
) -> heed::Result<()> {
|
||||
let search_function = match order_by {
|
||||
OrderBy::Lexicographic => lexicographically_iterate_over_facet_distribution,
|
||||
OrderBy::Count => count_iterate_over_facet_distribution,
|
||||
};
|
||||
|
||||
search_function(
|
||||
self.rtxn,
|
||||
self.index.facet_id_f64_docids.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>(),
|
||||
field_id,
|
||||
candidates,
|
||||
|facet_key, nbr_docids, _| {
|
||||
let facet_key = OrderedF64Codec::bytes_decode(facet_key).unwrap();
|
||||
distribution.insert(facet_key.to_string(), nbr_docids);
|
||||
if distribution.len() == self.max_values_per_facet {
|
||||
Ok(ControlFlow::Break(()))
|
||||
} else {
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn facet_strings_distribution_from_facet_levels(
|
||||
&self,
|
||||
field_id: FieldId,
|
||||
candidates: &RoaringBitmap,
|
||||
order_by: OrderBy,
|
||||
distribution: &mut IndexMap<String, u64>,
|
||||
) -> heed::Result<()> {
|
||||
let search_function = match order_by {
|
||||
OrderBy::Lexicographic => lexicographically_iterate_over_facet_distribution,
|
||||
OrderBy::Count => count_iterate_over_facet_distribution,
|
||||
};
|
||||
|
||||
search_function(
|
||||
self.rtxn,
|
||||
self.index.facet_id_string_docids.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>(),
|
||||
field_id,
|
||||
candidates,
|
||||
|facet_key, nbr_docids, any_docid| {
|
||||
let facet_key = StrRefCodec::bytes_decode(facet_key).unwrap();
|
||||
|
||||
let key: (FieldId, _, &str) = (field_id, any_docid, facet_key);
|
||||
let original_string = self
|
||||
.index
|
||||
.field_id_docid_facet_strings
|
||||
.get(self.rtxn, &key)?
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
distribution.insert(original_string, nbr_docids);
|
||||
if distribution.len() == self.max_values_per_facet {
|
||||
Ok(ControlFlow::Break(()))
|
||||
} else {
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn facet_values(
|
||||
&self,
|
||||
field_id: FieldId,
|
||||
order_by: OrderBy,
|
||||
) -> heed::Result<IndexMap<String, u64>> {
|
||||
use FacetType::{Number, String};
|
||||
|
||||
let mut distribution = IndexMap::new();
|
||||
match (order_by, &self.candidates) {
|
||||
(OrderBy::Lexicographic, Some(cnd)) if cnd.len() <= CANDIDATES_THRESHOLD => {
|
||||
// Classic search, candidates were specified, we must return facet values only related
|
||||
// to those candidates. We also enter here for facet strings for performance reasons.
|
||||
self.facet_distribution_from_documents(field_id, Number, cnd, &mut distribution)?;
|
||||
self.facet_distribution_from_documents(field_id, String, cnd, &mut distribution)?;
|
||||
}
|
||||
_ => {
|
||||
let universe;
|
||||
let candidates = match &self.candidates {
|
||||
Some(cnd) => cnd,
|
||||
None => {
|
||||
universe = self.index.documents_ids(self.rtxn)?;
|
||||
&universe
|
||||
}
|
||||
};
|
||||
|
||||
self.facet_numbers_distribution_from_facet_levels(
|
||||
field_id,
|
||||
candidates,
|
||||
order_by,
|
||||
&mut distribution,
|
||||
)?;
|
||||
self.facet_strings_distribution_from_facet_levels(
|
||||
field_id,
|
||||
candidates,
|
||||
order_by,
|
||||
&mut distribution,
|
||||
)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(distribution)
|
||||
}
|
||||
|
||||
pub fn compute_stats(&self) -> Result<BTreeMap<String, (f64, f64)>> {
|
||||
let fields_ids_map = self.index.fields_ids_map(self.rtxn)?;
|
||||
let filterable_fields = self.index.filterable_fields(self.rtxn)?;
|
||||
let candidates = if let Some(candidates) = self.candidates.clone() {
|
||||
candidates
|
||||
} else {
|
||||
return Ok(Default::default());
|
||||
};
|
||||
|
||||
let fields = match &self.facets {
|
||||
Some(facets) => {
|
||||
let invalid_fields: HashSet<_> = facets
|
||||
.iter()
|
||||
.map(|(name, _)| name)
|
||||
.filter(|facet| !crate::is_faceted(facet, &filterable_fields))
|
||||
.collect();
|
||||
if !invalid_fields.is_empty() {
|
||||
return Err(UserError::InvalidFacetsDistribution {
|
||||
invalid_facets_name: invalid_fields.into_iter().cloned().collect(),
|
||||
valid_facets_name: filterable_fields.into_iter().collect(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
facets.iter().map(|(name, _)| name).cloned().collect()
|
||||
}
|
||||
}
|
||||
None => filterable_fields,
|
||||
};
|
||||
|
||||
let mut distribution = BTreeMap::new();
|
||||
for (fid, name) in fields_ids_map.iter() {
|
||||
if crate::is_faceted(name, &fields) {
|
||||
let min_value = if let Some(min_value) = crate::search::facet::facet_min_value(
|
||||
self.index,
|
||||
self.rtxn,
|
||||
fid,
|
||||
candidates.clone(),
|
||||
)? {
|
||||
min_value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let max_value = if let Some(max_value) = crate::search::facet::facet_max_value(
|
||||
self.index,
|
||||
self.rtxn,
|
||||
fid,
|
||||
candidates.clone(),
|
||||
)? {
|
||||
max_value
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
distribution.insert(name.to_string(), (min_value, max_value));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(distribution)
|
||||
}
|
||||
|
||||
pub fn execute(&self) -> Result<BTreeMap<String, IndexMap<String, u64>>> {
|
||||
let fields_ids_map = self.index.fields_ids_map(self.rtxn)?;
|
||||
let filterable_fields = self.index.filterable_fields(self.rtxn)?;
|
||||
|
||||
let fields = match self.facets {
|
||||
Some(ref facets) => {
|
||||
let invalid_fields: HashSet<_> = facets
|
||||
.iter()
|
||||
.map(|(name, _)| name)
|
||||
.filter(|facet| !crate::is_faceted(facet, &filterable_fields))
|
||||
.collect();
|
||||
if !invalid_fields.is_empty() {
|
||||
return Err(UserError::InvalidFacetsDistribution {
|
||||
invalid_facets_name: invalid_fields.into_iter().cloned().collect(),
|
||||
valid_facets_name: filterable_fields.into_iter().collect(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
facets.iter().map(|(name, _)| name).cloned().collect()
|
||||
}
|
||||
}
|
||||
None => filterable_fields,
|
||||
};
|
||||
|
||||
let mut distribution = BTreeMap::new();
|
||||
for (fid, name) in fields_ids_map.iter() {
|
||||
if crate::is_faceted(name, &fields) {
|
||||
let order_by = self
|
||||
.facets
|
||||
.as_ref()
|
||||
.and_then(|facets| facets.get(name).copied())
|
||||
.unwrap_or(self.default_order_by);
|
||||
let values = self.facet_values(fid, order_by)?;
|
||||
distribution.insert(name.to_string(), values);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(distribution)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FacetDistribution<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let FacetDistribution {
|
||||
facets,
|
||||
candidates,
|
||||
max_values_per_facet,
|
||||
default_order_by,
|
||||
rtxn: _,
|
||||
index: _,
|
||||
} = self;
|
||||
|
||||
f.debug_struct("FacetDistribution")
|
||||
.field("facets", facets)
|
||||
.field("candidates", candidates)
|
||||
.field("max_values_per_facet", max_values_per_facet)
|
||||
.field("default_order_by", default_order_by)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter;
|
||||
|
||||
use big_s::S;
|
||||
use maplit::hashset;
|
||||
|
||||
use crate::documents::documents_batch_reader_from_objects;
|
||||
use crate::index::tests::TempIndex;
|
||||
use crate::{milli_snap, FacetDistribution, OrderBy};
|
||||
|
||||
#[test]
|
||||
fn few_candidates_few_facet_values() {
|
||||
// All the tests here avoid using the code in `facet_distribution_iter` because there aren't
|
||||
// enough candidates.
|
||||
|
||||
let mut index = TempIndex::new();
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let documents = documents!([
|
||||
{ "colour": "Blue" },
|
||||
{ "colour": " blue" },
|
||||
{ "colour": "RED" }
|
||||
]);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2, "RED": 1}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates([0, 1, 2].iter().copied().collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2, "RED": 1}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates([1, 2].iter().copied().collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
// I think it would be fine if " blue" was "Blue" instead.
|
||||
// We just need to get any non-normalised string I think, even if it's not in
|
||||
// the candidates
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {" blue": 1, "RED": 1}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates([2].iter().copied().collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"RED": 1}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates([0, 1, 2].iter().copied().collect())
|
||||
.max_values_per_facet(1)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::Count)))
|
||||
.candidates([0, 1, 2].iter().copied().collect())
|
||||
.max_values_per_facet(1)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2}}"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_candidates_few_facet_values() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = ["Red", "RED", " red ", "Blue", "BLUE"];
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..10_000 {
|
||||
let document = serde_json::json!({
|
||||
"colour": facet_values[i % 5],
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 4000, "Red": 6000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.max_values_per_facet(1)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 4000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..10_000).collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 4000, "Red": 6000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..5_000).collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2000, "Red": 3000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..5_000).collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2000, "Red": 3000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..5_000).collect())
|
||||
.max_values_per_facet(1)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Blue": 2000}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::Count)))
|
||||
.candidates((0..5_000).collect())
|
||||
.max_values_per_facet(1)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), @r###"{"colour": {"Red": 3000}}"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_candidates_many_facet_values() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = (0..1000).map(|x| format!("{x:x}")).collect::<Vec<_>>();
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..10_000 {
|
||||
let document = serde_json::json!({
|
||||
"colour": facet_values[i % 1000],
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates", @"ac9229ed5964d893af96a7076e2f8af5");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.max_values_per_facet(2)
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates_with_max_2", @r###"{"colour": {"0": 10, "1": 10}}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..10_000).collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_10_000", @"ac9229ed5964d893af96a7076e2f8af5");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..5_000).collect())
|
||||
.execute()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_5_000", @"825f23a4090d05756f46176987b7d992");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn facet_stats() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = (0..1000).collect::<Vec<_>>();
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..1000 {
|
||||
let document = serde_json::json!({
|
||||
"colour": facet_values[i % 1000],
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates", @"{}");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..1000).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_1000", @r###"{"colour": (0.0, 999.0)}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((217..777).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_217_777", @r###"{"colour": (217.0, 776.0)}"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn facet_stats_array() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = (0..1000).collect::<Vec<_>>();
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..1000 {
|
||||
let document = serde_json::json!({
|
||||
"colour": [facet_values[i % 1000], facet_values[i % 1000] + 1000],
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates", @"{}");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..1000).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_1000", @r###"{"colour": (0.0, 1999.0)}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((217..777).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_217_777", @r###"{"colour": (217.0, 1776.0)}"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn facet_stats_mixed_array() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = (0..1000).collect::<Vec<_>>();
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..1000 {
|
||||
let document = serde_json::json!({
|
||||
"colour": [facet_values[i % 1000], format!("{}", facet_values[i % 1000] + 1000)],
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates", @"{}");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..1000).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_1000", @r###"{"colour": (0.0, 999.0)}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((217..777).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_217_777", @r###"{"colour": (217.0, 776.0)}"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn facet_mixed_values() {
|
||||
let mut index = TempIndex::new_with_map_size(4096 * 10_000);
|
||||
index.index_documents_config.autogenerate_docids = true;
|
||||
|
||||
index
|
||||
.update_settings(|settings| settings.set_filterable_fields(hashset! { S("colour") }))
|
||||
.unwrap();
|
||||
|
||||
let facet_values = (0..1000).collect::<Vec<_>>();
|
||||
|
||||
let mut documents = vec![];
|
||||
for i in 0..1000 {
|
||||
let document = if i % 2 == 0 {
|
||||
serde_json::json!({
|
||||
"colour": [facet_values[i % 1000], facet_values[i % 1000] + 1000],
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"colour": format!("{}", facet_values[i % 1000] + 10000),
|
||||
})
|
||||
};
|
||||
let document = document.as_object().unwrap().clone();
|
||||
documents.push(document);
|
||||
}
|
||||
|
||||
let documents = documents_batch_reader_from_objects(documents);
|
||||
|
||||
index.add_documents(documents).unwrap();
|
||||
|
||||
let txn = index.read_txn().unwrap();
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "no_candidates", @"{}");
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((0..1000).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_0_1000", @r###"{"colour": (0.0, 1998.0)}"###);
|
||||
|
||||
let map = FacetDistribution::new(&txn, &index)
|
||||
.facets(iter::once(("colour", OrderBy::default())))
|
||||
.candidates((217..777).collect())
|
||||
.compute_stats()
|
||||
.unwrap();
|
||||
|
||||
milli_snap!(format!("{map:?}"), "candidates_217_777", @r###"{"colour": (218.0, 1776.0)}"###);
|
||||
}
|
||||
}
|
301
crates/milli/src/search/facet/facet_distribution_iter.rs
Normal file
301
crates/milli/src/search/facet/facet_distribution_iter.rs
Normal file
@ -0,0 +1,301 @@
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use heed::Result;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::{get_first_facet_value, get_highest_level};
|
||||
use crate::heed_codec::facet::{
|
||||
FacetGroupKey, FacetGroupKeyCodec, FacetGroupLazyValueCodec, FacetGroupValueCodec,
|
||||
};
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
use crate::{CboRoaringBitmapCodec, DocumentId};
|
||||
|
||||
/// Call the given closure on the facet distribution of the candidate documents.
|
||||
///
|
||||
/// The arguments to the closure are:
|
||||
/// - the facet value, as a byte slice
|
||||
/// - the number of documents among the candidates that contain this facet value
|
||||
/// - the id of a document which contains the facet value. Note that this document
|
||||
/// is not necessarily from the list of candidates, it is simply *any* document which
|
||||
/// contains this facet value.
|
||||
///
|
||||
/// The return value of the closure is a `ControlFlow<()>` which indicates whether we should
|
||||
/// keep iterating over the different facet values or stop.
|
||||
pub fn lexicographically_iterate_over_facet_distribution<'t, CB>(
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
candidates: &RoaringBitmap,
|
||||
callback: CB,
|
||||
) -> Result<()>
|
||||
where
|
||||
CB: FnMut(&'t [u8], u64, DocumentId) -> Result<ControlFlow<()>>,
|
||||
{
|
||||
let db = db.remap_data_type::<FacetGroupLazyValueCodec>();
|
||||
let mut fd = LexicographicFacetDistribution { rtxn, db, field_id, callback };
|
||||
let highest_level = get_highest_level(rtxn, db, field_id)?;
|
||||
|
||||
if let Some(first_bound) = get_first_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)? {
|
||||
fd.iterate(candidates, highest_level, first_bound, usize::MAX)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count_iterate_over_facet_distribution<'t, CB>(
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
candidates: &RoaringBitmap,
|
||||
mut callback: CB,
|
||||
) -> Result<()>
|
||||
where
|
||||
CB: FnMut(&'t [u8], u64, DocumentId) -> Result<ControlFlow<()>>,
|
||||
{
|
||||
/// # Important
|
||||
/// The order of the fields determines the order in which the facet values will be returned.
|
||||
/// This struct is inserted in a BinaryHeap and popped later on.
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
struct LevelEntry<'t> {
|
||||
/// The number of candidates in this entry.
|
||||
count: u64,
|
||||
/// The key level of the entry.
|
||||
level: Reverse<u8>,
|
||||
/// The left bound key.
|
||||
left_bound: &'t [u8],
|
||||
/// The number of keys we must look for after `left_bound`.
|
||||
group_size: u8,
|
||||
/// Any docid in the set of matching documents. Used to find the original facet string.
|
||||
any_docid: u32,
|
||||
}
|
||||
|
||||
// Represents the list of keys that we must explore.
|
||||
let mut heap = BinaryHeap::new();
|
||||
let db = db.remap_data_type::<FacetGroupLazyValueCodec>();
|
||||
let highest_level = get_highest_level(rtxn, db, field_id)?;
|
||||
|
||||
if let Some(first_bound) = get_first_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)? {
|
||||
// We first fill the heap with values from the highest level
|
||||
let starting_key =
|
||||
FacetGroupKey { field_id, level: highest_level, left_bound: first_bound };
|
||||
for el in db.range(rtxn, &(&starting_key..))?.take(usize::MAX) {
|
||||
let (key, value) = el?;
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if key.field_id != field_id {
|
||||
break;
|
||||
}
|
||||
let intersection = CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
value.bitmap_bytes,
|
||||
candidates,
|
||||
)?;
|
||||
let count = intersection.len();
|
||||
if count != 0 {
|
||||
heap.push(LevelEntry {
|
||||
count,
|
||||
level: Reverse(key.level),
|
||||
left_bound: key.left_bound,
|
||||
group_size: value.size,
|
||||
any_docid: intersection.min().unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(LevelEntry { count, level, left_bound, group_size, any_docid }) = heap.pop()
|
||||
{
|
||||
if let Reverse(0) = level {
|
||||
match (callback)(left_bound, count, any_docid)? {
|
||||
ControlFlow::Continue(_) => (),
|
||||
ControlFlow::Break(_) => return Ok(()),
|
||||
}
|
||||
} else {
|
||||
let starting_key = FacetGroupKey { field_id, level: level.0 - 1, left_bound };
|
||||
for el in db.range(rtxn, &(&starting_key..))?.take(group_size as usize) {
|
||||
let (key, value) = el?;
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if key.field_id != field_id {
|
||||
break;
|
||||
}
|
||||
let intersection = CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
value.bitmap_bytes,
|
||||
candidates,
|
||||
)?;
|
||||
let count = intersection.len();
|
||||
if count != 0 {
|
||||
heap.push(LevelEntry {
|
||||
count,
|
||||
level: Reverse(key.level),
|
||||
left_bound: key.left_bound,
|
||||
group_size: value.size,
|
||||
any_docid: intersection.min().unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Iterate over the facets values by lexicographic order.
|
||||
struct LexicographicFacetDistribution<'t, CB>
|
||||
where
|
||||
CB: FnMut(&'t [u8], u64, DocumentId) -> Result<ControlFlow<()>>,
|
||||
{
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupLazyValueCodec>,
|
||||
field_id: u16,
|
||||
callback: CB,
|
||||
}
|
||||
|
||||
impl<'t, CB> LexicographicFacetDistribution<'t, CB>
|
||||
where
|
||||
CB: FnMut(&'t [u8], u64, DocumentId) -> Result<ControlFlow<()>>,
|
||||
{
|
||||
fn iterate_level_0(
|
||||
&mut self,
|
||||
candidates: &RoaringBitmap,
|
||||
starting_bound: &'t [u8],
|
||||
group_size: usize,
|
||||
) -> Result<ControlFlow<()>> {
|
||||
let starting_key =
|
||||
FacetGroupKey { field_id: self.field_id, level: 0, left_bound: starting_bound };
|
||||
let iter = self.db.range(self.rtxn, &(starting_key..))?.take(group_size);
|
||||
for el in iter {
|
||||
let (key, value) = el?;
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if key.field_id != self.field_id {
|
||||
return Ok(ControlFlow::Break(()));
|
||||
}
|
||||
let docids_in_common = CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
value.bitmap_bytes,
|
||||
candidates,
|
||||
)?;
|
||||
if !docids_in_common.is_empty() {
|
||||
let any_docid_in_common = docids_in_common.min().unwrap();
|
||||
match (self.callback)(key.left_bound, docids_in_common.len(), any_docid_in_common)?
|
||||
{
|
||||
ControlFlow::Continue(_) => (),
|
||||
ControlFlow::Break(_) => return Ok(ControlFlow::Break(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
|
||||
fn iterate(
|
||||
&mut self,
|
||||
candidates: &RoaringBitmap,
|
||||
level: u8,
|
||||
starting_bound: &'t [u8],
|
||||
group_size: usize,
|
||||
) -> Result<ControlFlow<()>> {
|
||||
if level == 0 {
|
||||
return self.iterate_level_0(candidates, starting_bound, group_size);
|
||||
}
|
||||
let starting_key =
|
||||
FacetGroupKey { field_id: self.field_id, level, left_bound: starting_bound };
|
||||
let iter = self.db.range(self.rtxn, &(&starting_key..))?.take(group_size);
|
||||
|
||||
for el in iter {
|
||||
let (key, value) = el?;
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if key.field_id != self.field_id {
|
||||
return Ok(ControlFlow::Break(()));
|
||||
}
|
||||
let docids_in_common = CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
value.bitmap_bytes,
|
||||
candidates,
|
||||
)?;
|
||||
if !docids_in_common.is_empty() {
|
||||
let cf = self.iterate(
|
||||
&docids_in_common,
|
||||
level - 1,
|
||||
key.left_bound,
|
||||
value.size as usize,
|
||||
)?;
|
||||
match cf {
|
||||
ControlFlow::Continue(_) => (),
|
||||
ControlFlow::Break(_) => return Ok(ControlFlow::Break(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use heed::BytesDecode;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::lexicographically_iterate_over_facet_distribution;
|
||||
use crate::heed_codec::facet::OrderedF64Codec;
|
||||
use crate::milli_snap;
|
||||
use crate::search::facet::tests::{get_random_looking_index, get_simple_index};
|
||||
|
||||
#[test]
|
||||
fn filter_distribution_all() {
|
||||
let indexes = [get_simple_index(), get_random_looking_index()];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (0..=255).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
lexicographically_iterate_over_facet_distribution(
|
||||
&txn,
|
||||
index.content,
|
||||
0,
|
||||
&candidates,
|
||||
|facet, count, _| {
|
||||
let facet = OrderedF64Codec::bytes_decode(facet).unwrap();
|
||||
results.push_str(&format!("{facet}: {count}\n"));
|
||||
Ok(ControlFlow::Continue(()))
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
milli_snap!(results, i);
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_distribution_all_stop_early() {
|
||||
let indexes = [get_simple_index(), get_random_looking_index()];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (0..=255).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
let mut nbr_facets = 0;
|
||||
lexicographically_iterate_over_facet_distribution(
|
||||
&txn,
|
||||
index.content,
|
||||
0,
|
||||
&candidates,
|
||||
|facet, count, _| {
|
||||
let facet = OrderedF64Codec::bytes_decode(facet).unwrap();
|
||||
if nbr_facets == 100 {
|
||||
Ok(ControlFlow::Break(()))
|
||||
} else {
|
||||
nbr_facets += 1;
|
||||
results.push_str(&format!("{facet}: {count}\n"));
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
milli_snap!(results, i);
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
688
crates/milli/src/search/facet/facet_range_search.rs
Normal file
688
crates/milli/src/search/facet/facet_range_search.rs
Normal file
@ -0,0 +1,688 @@
|
||||
use std::ops::{Bound, RangeBounds};
|
||||
|
||||
use heed::BytesEncode;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::{get_first_facet_value, get_highest_level, get_last_facet_value};
|
||||
use crate::heed_codec::facet::{
|
||||
FacetGroupKey, FacetGroupKeyCodec, FacetGroupLazyValueCodec, FacetGroupValueCodec,
|
||||
};
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
use crate::{CboRoaringBitmapCodec, Result};
|
||||
|
||||
/// Find all the document ids for which the given field contains a value contained within
|
||||
/// the two bounds.
|
||||
pub fn find_docids_of_facet_within_bounds<'t, BoundCodec>(
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BoundCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
left: &'t Bound<<BoundCodec as BytesEncode<'t>>::EItem>,
|
||||
right: &'t Bound<<BoundCodec as BytesEncode<'t>>::EItem>,
|
||||
universe: Option<&RoaringBitmap>,
|
||||
docids: &mut RoaringBitmap,
|
||||
) -> Result<()>
|
||||
where
|
||||
BoundCodec: for<'a> BytesEncode<'a>,
|
||||
for<'a> <BoundCodec as BytesEncode<'a>>::EItem: Sized,
|
||||
{
|
||||
let inner;
|
||||
let left = match left {
|
||||
Bound::Included(left) => {
|
||||
inner = BoundCodec::bytes_encode(left).map_err(heed::Error::Encoding)?;
|
||||
Bound::Included(inner.as_ref())
|
||||
}
|
||||
Bound::Excluded(left) => {
|
||||
inner = BoundCodec::bytes_encode(left).map_err(heed::Error::Encoding)?;
|
||||
Bound::Excluded(inner.as_ref())
|
||||
}
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
};
|
||||
let inner;
|
||||
let right = match right {
|
||||
Bound::Included(right) => {
|
||||
inner = BoundCodec::bytes_encode(right).map_err(heed::Error::Encoding)?;
|
||||
Bound::Included(inner.as_ref())
|
||||
}
|
||||
Bound::Excluded(right) => {
|
||||
inner = BoundCodec::bytes_encode(right).map_err(heed::Error::Encoding)?;
|
||||
Bound::Excluded(inner.as_ref())
|
||||
}
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
};
|
||||
let db = db.remap_types::<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupLazyValueCodec>();
|
||||
let mut f = FacetRangeSearch { rtxn, db, field_id, left, right, universe, docids };
|
||||
let highest_level = get_highest_level(rtxn, db, field_id)?;
|
||||
|
||||
if let Some(starting_left_bound) =
|
||||
get_first_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)?
|
||||
{
|
||||
let rightmost_bound =
|
||||
Bound::Included(get_last_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)?.unwrap()); // will not fail because get_first_facet_value succeeded
|
||||
let group_size = usize::MAX;
|
||||
f.run(highest_level, starting_left_bound, rightmost_bound, group_size)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the document ids that have a facet with a value between the two given bounds
|
||||
struct FacetRangeSearch<'t, 'b, 'bitmap> {
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupLazyValueCodec>,
|
||||
field_id: u16,
|
||||
left: Bound<&'b [u8]>,
|
||||
right: Bound<&'b [u8]>,
|
||||
/// The subset of documents ids that are useful for this search.
|
||||
/// Great performance optimizations can be achieved by only fetching values matching this subset.
|
||||
universe: Option<&'bitmap RoaringBitmap>,
|
||||
docids: &'bitmap mut RoaringBitmap,
|
||||
}
|
||||
|
||||
impl<'t, 'b, 'bitmap> FacetRangeSearch<'t, 'b, 'bitmap> {
|
||||
fn run_level_0(&mut self, starting_left_bound: &'t [u8], group_size: usize) -> Result<()> {
|
||||
let left_key =
|
||||
FacetGroupKey { field_id: self.field_id, level: 0, left_bound: starting_left_bound };
|
||||
let iter = self.db.range(self.rtxn, &(left_key..))?.take(group_size);
|
||||
for el in iter {
|
||||
let (key, value) = el?;
|
||||
// the right side of the iter range is unbounded, so we need to make sure that we are not iterating
|
||||
// on the next field id
|
||||
if key.field_id != self.field_id {
|
||||
return Ok(());
|
||||
}
|
||||
let should_skip = {
|
||||
match self.left {
|
||||
Bound::Included(left) => left > key.left_bound,
|
||||
Bound::Excluded(left) => left >= key.left_bound,
|
||||
Bound::Unbounded => false,
|
||||
}
|
||||
};
|
||||
if should_skip {
|
||||
continue;
|
||||
}
|
||||
let should_stop = {
|
||||
match self.right {
|
||||
Bound::Included(right) => right < key.left_bound,
|
||||
Bound::Excluded(right) => right <= key.left_bound,
|
||||
Bound::Unbounded => false,
|
||||
}
|
||||
};
|
||||
if should_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
if RangeBounds::<&[u8]>::contains(&(self.left, self.right), &key.left_bound) {
|
||||
*self.docids |= match self.universe {
|
||||
Some(universe) => CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
value.bitmap_bytes,
|
||||
universe,
|
||||
)?,
|
||||
None => CboRoaringBitmapCodec::deserialize_from(value.bitmap_bytes)?,
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursive part of the algorithm for level > 0.
|
||||
///
|
||||
/// It works by visiting a slice of a level and checking whether the range asscociated
|
||||
/// with each visited element is contained within the bounds.
|
||||
///
|
||||
/// 1. So long as the element's range is less than the left bound, we do nothing and keep iterating
|
||||
/// 2. If the element's range is fully contained by the bounds, then all of its docids are added to
|
||||
/// the roaring bitmap.
|
||||
/// 3. If the element's range merely intersects the bounds, then we call the algorithm recursively
|
||||
/// on the children of the element from the level below.
|
||||
/// 4. If the element's range is greater than the right bound, we do nothing and stop iterating.
|
||||
/// Note that the right bound is found through either the `left_bound` of the *next* element,
|
||||
/// or from the `rightmost_bound` argument
|
||||
///
|
||||
/// ## Arguments
|
||||
/// - `level`: the level being visited
|
||||
/// - `starting_left_bound`: the left_bound of the first element to visit
|
||||
/// - `rightmost_bound`: the right bound of the last element that should be visited
|
||||
/// - `group_size`: the number of elements that should be visited
|
||||
fn run(
|
||||
&mut self,
|
||||
level: u8,
|
||||
starting_left_bound: &'t [u8],
|
||||
rightmost_bound: Bound<&'t [u8]>,
|
||||
group_size: usize,
|
||||
) -> Result<()> {
|
||||
if level == 0 {
|
||||
return self.run_level_0(starting_left_bound, group_size);
|
||||
}
|
||||
|
||||
let left_key =
|
||||
FacetGroupKey { field_id: self.field_id, level, left_bound: starting_left_bound };
|
||||
let mut iter = self.db.range(self.rtxn, &(left_key..))?.take(group_size);
|
||||
|
||||
// We iterate over the range while keeping in memory the previous value
|
||||
let (mut previous_key, mut previous_value) = iter.next().unwrap()?;
|
||||
for el in iter {
|
||||
let (next_key, next_value) = el?;
|
||||
// the right of the iter range is potentially unbounded (e.g. if `group_size` is usize::MAX),
|
||||
// so we need to make sure that we are not iterating on the next field id
|
||||
if next_key.field_id != self.field_id {
|
||||
break;
|
||||
}
|
||||
// now, do we skip, stop, or visit?
|
||||
let should_skip = {
|
||||
match self.left {
|
||||
Bound::Included(left) => left >= next_key.left_bound,
|
||||
Bound::Excluded(left) => left >= next_key.left_bound,
|
||||
Bound::Unbounded => false,
|
||||
}
|
||||
};
|
||||
if should_skip {
|
||||
previous_key = next_key;
|
||||
previous_value = next_value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// should we stop?
|
||||
// We should if the search range doesn't include any
|
||||
// element from the previous key or its successors
|
||||
let should_stop = {
|
||||
match self.right {
|
||||
Bound::Included(right) => right < previous_key.left_bound,
|
||||
Bound::Excluded(right) => right <= previous_key.left_bound,
|
||||
Bound::Unbounded => false,
|
||||
}
|
||||
};
|
||||
if should_stop {
|
||||
return Ok(());
|
||||
}
|
||||
// should we take the whole thing, without recursing down?
|
||||
let should_take_whole_group = {
|
||||
let left_condition = match self.left {
|
||||
Bound::Included(left) => previous_key.left_bound >= left,
|
||||
Bound::Excluded(left) => previous_key.left_bound > left,
|
||||
Bound::Unbounded => true,
|
||||
};
|
||||
let right_condition = match self.right {
|
||||
Bound::Included(right) => next_key.left_bound <= right,
|
||||
Bound::Excluded(right) => next_key.left_bound <= right,
|
||||
Bound::Unbounded => true,
|
||||
};
|
||||
left_condition && right_condition
|
||||
};
|
||||
if should_take_whole_group {
|
||||
*self.docids |= match self.universe {
|
||||
Some(universe) => CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
previous_value.bitmap_bytes,
|
||||
universe,
|
||||
)?,
|
||||
None => CboRoaringBitmapCodec::deserialize_from(previous_value.bitmap_bytes)?,
|
||||
};
|
||||
previous_key = next_key;
|
||||
previous_value = next_value;
|
||||
continue;
|
||||
}
|
||||
// from here, we should visit the children of the previous element and
|
||||
// call the function recursively
|
||||
|
||||
let level = level - 1;
|
||||
let starting_left_bound = previous_key.left_bound;
|
||||
let rightmost_bound = Bound::Excluded(next_key.left_bound);
|
||||
let group_size = previous_value.size as usize;
|
||||
|
||||
self.run(level, starting_left_bound, rightmost_bound, group_size)?;
|
||||
|
||||
previous_key = next_key;
|
||||
previous_value = next_value;
|
||||
}
|
||||
// previous_key/previous_value are the last element's key/value
|
||||
|
||||
// now, do we skip, stop, or visit?
|
||||
let should_skip = {
|
||||
match (self.left, rightmost_bound) {
|
||||
(Bound::Included(left), Bound::Included(right)) => left > right,
|
||||
(Bound::Included(left), Bound::Excluded(right)) => left >= right,
|
||||
(Bound::Excluded(left), Bound::Included(right) | Bound::Excluded(right)) => {
|
||||
left >= right
|
||||
}
|
||||
(Bound::Unbounded, _) => false,
|
||||
(_, Bound::Unbounded) => false, // should never run?
|
||||
}
|
||||
};
|
||||
if should_skip {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// should we stop?
|
||||
// We should if the search range doesn't include any
|
||||
// element from the previous key or its successors
|
||||
let should_stop = {
|
||||
match self.right {
|
||||
Bound::Included(right) => right < previous_key.left_bound,
|
||||
Bound::Excluded(right) => right <= previous_key.left_bound,
|
||||
Bound::Unbounded => false,
|
||||
}
|
||||
};
|
||||
if should_stop {
|
||||
return Ok(());
|
||||
}
|
||||
// should we take the whole thing, without recursing down?
|
||||
let should_take_whole_group = {
|
||||
let left_condition = match self.left {
|
||||
Bound::Included(left) => previous_key.left_bound >= left,
|
||||
Bound::Excluded(left) => previous_key.left_bound > left,
|
||||
Bound::Unbounded => true,
|
||||
};
|
||||
let right_condition = match (self.right, rightmost_bound) {
|
||||
(Bound::Included(right), Bound::Included(rightmost)) => {
|
||||
// we need to stay within the bound ..=right
|
||||
// the element's range goes to ..=righmost
|
||||
// so the element fits entirely within the bound if rightmost <= right
|
||||
rightmost <= right
|
||||
}
|
||||
(Bound::Included(right), Bound::Excluded(rightmost)) => {
|
||||
// we need to stay within the bound ..=right
|
||||
// the element's range goes to ..righmost
|
||||
// so the element fits entirely within the bound if rightmost <= right
|
||||
rightmost <= right
|
||||
}
|
||||
(Bound::Excluded(right), Bound::Included(rightmost)) => {
|
||||
// we need to stay within the bound ..right
|
||||
// the element's range goes to ..=righmost
|
||||
// so the element fits entirely within the bound if rightmost < right
|
||||
rightmost < right
|
||||
}
|
||||
(Bound::Excluded(right), Bound::Excluded(rightmost)) => {
|
||||
// we need to stay within the bound ..right
|
||||
// the element's range goes to ..righmost
|
||||
// so the element fits entirely within the bound if rightmost <= right
|
||||
rightmost <= right
|
||||
}
|
||||
(Bound::Unbounded, _) => {
|
||||
// we need to stay within the bound ..inf
|
||||
// so the element always fits entirely within the bound
|
||||
true
|
||||
}
|
||||
(_, Bound::Unbounded) => {
|
||||
// we need to stay within a finite bound
|
||||
// but the element's range goes to ..inf
|
||||
// so the element never fits entirely within the bound
|
||||
false
|
||||
}
|
||||
};
|
||||
left_condition && right_condition
|
||||
};
|
||||
if should_take_whole_group {
|
||||
*self.docids |= match self.universe {
|
||||
Some(universe) => CboRoaringBitmapCodec::intersection_with_serialized(
|
||||
previous_value.bitmap_bytes,
|
||||
universe,
|
||||
)?,
|
||||
None => CboRoaringBitmapCodec::deserialize_from(previous_value.bitmap_bytes)?,
|
||||
};
|
||||
} else {
|
||||
let level = level - 1;
|
||||
let starting_left_bound = previous_key.left_bound;
|
||||
let group_size = previous_value.size as usize;
|
||||
|
||||
self.run(level, starting_left_bound, rightmost_bound, group_size)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Bound;
|
||||
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::find_docids_of_facet_within_bounds;
|
||||
use crate::heed_codec::facet::{FacetGroupKeyCodec, OrderedF64Codec};
|
||||
use crate::milli_snap;
|
||||
use crate::search::facet::tests::{
|
||||
get_random_looking_index, get_random_looking_index_with_multiple_field_ids,
|
||||
get_simple_index, get_simple_index_with_multiple_field_ids,
|
||||
};
|
||||
use crate::snapshot_tests::display_bitmap;
|
||||
|
||||
#[test]
|
||||
fn random_looking_index_snap() {
|
||||
let index = get_random_looking_index();
|
||||
milli_snap!(format!("{index}"), @"3256c76a7c1b768a013e78d5fa6e9ff9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_looking_index_with_multiple_field_ids_snap() {
|
||||
let index = get_random_looking_index_with_multiple_field_ids();
|
||||
milli_snap!(format!("{index}"), @"c3e5fe06a8f1c404ed4935b32c90a89b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_index_snap() {
|
||||
let index = get_simple_index();
|
||||
milli_snap!(format!("{index}"), @"5dbfa134cc44abeb3ab6242fc182e48e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_index_with_multiple_field_ids_snap() {
|
||||
let index = get_simple_index_with_multiple_field_ids();
|
||||
milli_snap!(format!("{index}"), @"a4893298218f682bc76357f46777448c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_range_increasing() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
get_random_looking_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let mut results = String::new();
|
||||
for i in 0..=255 {
|
||||
let i = i as f64;
|
||||
let start = Bound::Included(0.);
|
||||
let end = Bound::Included(i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results.push_str(&format!("0 <= . <= {i} : {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
milli_snap!(results, format!("included_{i}"));
|
||||
let mut results = String::new();
|
||||
for i in 0..=255 {
|
||||
let i = i as f64;
|
||||
let start = Bound::Excluded(0.);
|
||||
let end = Bound::Excluded(i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results.push_str(&format!("0 < . < {i} : {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
milli_snap!(results, format!("excluded_{i}"));
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn filter_range_decreasing() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
get_random_looking_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
|
||||
let mut results = String::new();
|
||||
|
||||
for i in (0..=255).rev() {
|
||||
let i = i as f64;
|
||||
let start = Bound::Included(i);
|
||||
let end = Bound::Included(255.);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
results.push_str(&format!("{i} <= . <= 255 : {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
|
||||
milli_snap!(results, format!("included_{i}"));
|
||||
|
||||
let mut results = String::new();
|
||||
|
||||
for i in (0..=255).rev() {
|
||||
let i = i as f64;
|
||||
let start = Bound::Excluded(i);
|
||||
let end = Bound::Excluded(255.);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
results.push_str(&format!("{i} < . < 255 : {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
|
||||
milli_snap!(results, format!("excluded_{i}"));
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn filter_range_pinch() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
get_random_looking_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
|
||||
let mut results = String::new();
|
||||
|
||||
for i in (0..=128).rev() {
|
||||
let i = i as f64;
|
||||
let start = Bound::Included(i);
|
||||
let end = Bound::Included(255. - i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
results.push_str(&format!(
|
||||
"{i} <= . <= {r} : {docids}\n",
|
||||
r = 255. - i,
|
||||
docids = display_bitmap(&docids)
|
||||
));
|
||||
}
|
||||
|
||||
milli_snap!(results, format!("included_{i}"));
|
||||
|
||||
let mut results = String::new();
|
||||
|
||||
for i in (0..=128).rev() {
|
||||
let i = i as f64;
|
||||
let start = Bound::Excluded(i);
|
||||
let end = Bound::Excluded(255. - i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
results.push_str(&format!(
|
||||
"{i} < . < {r} {docids}\n",
|
||||
r = 255. - i,
|
||||
docids = display_bitmap(&docids)
|
||||
));
|
||||
}
|
||||
|
||||
milli_snap!(results, format!("excluded_{i}"));
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_range_unbounded() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
get_random_looking_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let mut results = String::new();
|
||||
for i in 0..=255 {
|
||||
let i = i as f64;
|
||||
let start = Bound::Included(i);
|
||||
let end = Bound::Unbounded;
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results.push_str(&format!(">= {i}: {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
milli_snap!(results, format!("start_from_included_{i}"));
|
||||
let mut results = String::new();
|
||||
for i in 0..=255 {
|
||||
let i = i as f64;
|
||||
let start = Bound::Unbounded;
|
||||
let end = Bound::Included(i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results.push_str(&format!("<= {i}: {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
milli_snap!(results, format!("end_at_included_{i}"));
|
||||
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&Bound::Unbounded,
|
||||
&Bound::Unbounded,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
milli_snap!(
|
||||
&format!("all field_id 0: {}\n", display_bitmap(&docids)),
|
||||
format!("unbounded_field_id_0_{i}")
|
||||
);
|
||||
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
1,
|
||||
&Bound::Unbounded,
|
||||
&Bound::Unbounded,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
milli_snap!(
|
||||
&format!("all field_id 1: {}\n", display_bitmap(&docids)),
|
||||
format!("unbounded_field_id_1_{i}")
|
||||
);
|
||||
|
||||
drop(txn);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_range_exact() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
get_random_looking_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let mut results_0 = String::new();
|
||||
let mut results_1 = String::new();
|
||||
for i in 0..=255 {
|
||||
let i = i as f64;
|
||||
let start = Bound::Included(i);
|
||||
let end = Bound::Included(i);
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
0,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results_0.push_str(&format!("{i}: {}\n", display_bitmap(&docids)));
|
||||
|
||||
let mut docids = RoaringBitmap::new();
|
||||
find_docids_of_facet_within_bounds::<OrderedF64Codec>(
|
||||
&txn,
|
||||
index.content.remap_key_type::<FacetGroupKeyCodec<OrderedF64Codec>>(),
|
||||
1,
|
||||
&start,
|
||||
&end,
|
||||
None,
|
||||
&mut docids,
|
||||
)
|
||||
.unwrap();
|
||||
#[allow(clippy::format_push_string)]
|
||||
results_1.push_str(&format!("{i}: {}\n", display_bitmap(&docids)));
|
||||
}
|
||||
milli_snap!(results_0, format!("field_id_0_exact_{i}"));
|
||||
milli_snap!(results_1, format!("field_id_1_exact_{i}"));
|
||||
|
||||
drop(txn);
|
||||
}
|
||||
}
|
||||
}
|
230
crates/milli/src/search/facet/facet_sort_ascending.rs
Normal file
230
crates/milli/src/search/facet/facet_sort_ascending.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use heed::Result;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::{get_first_facet_value, get_highest_level};
|
||||
use crate::heed_codec::facet::{
|
||||
FacetGroupKey, FacetGroupKeyCodec, FacetGroupValue, FacetGroupValueCodec,
|
||||
};
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
|
||||
/// Return an iterator which iterates over the given candidate documents in
|
||||
/// ascending order of their facet value for the given field id.
|
||||
///
|
||||
/// The documents returned by the iterator are grouped by the facet values that
|
||||
/// determined their rank. For example, given the documents:
|
||||
///
|
||||
/// ```text
|
||||
/// 0: { "colour": ["blue", "green"] }
|
||||
/// 1: { "colour": ["blue", "red"] }
|
||||
/// 2: { "colour": ["orange", "red"] }
|
||||
/// 3: { "colour": ["green", "red"] }
|
||||
/// 4: { "colour": ["blue", "orange", "red"] }
|
||||
/// ```
|
||||
/// Then calling the function on the candidates `[0, 2, 3, 4]` will return an iterator
|
||||
/// over the following elements:
|
||||
/// ```text
|
||||
/// [0, 4] // corresponds to all the documents within the candidates that have the facet value "blue"
|
||||
/// [3] // same for "green"
|
||||
/// [2] // same for "orange"
|
||||
/// END
|
||||
/// ```
|
||||
/// Note that once a document id is returned by the iterator, it is never returned again.
|
||||
pub fn ascending_facet_sort<'t>(
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
candidates: RoaringBitmap,
|
||||
) -> Result<impl Iterator<Item = Result<(RoaringBitmap, &'t [u8])>> + 't> {
|
||||
let highest_level = get_highest_level(rtxn, db, field_id)?;
|
||||
if let Some(first_bound) = get_first_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)? {
|
||||
let first_key = FacetGroupKey { field_id, level: highest_level, left_bound: first_bound };
|
||||
let iter = db.range(rtxn, &(first_key..)).unwrap().take(usize::MAX);
|
||||
|
||||
Ok(itertools::Either::Left(AscendingFacetSort {
|
||||
rtxn,
|
||||
db,
|
||||
field_id,
|
||||
stack: vec![(candidates, iter)],
|
||||
}))
|
||||
} else {
|
||||
Ok(itertools::Either::Right(std::iter::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
struct AscendingFacetSort<'t, 'e> {
|
||||
rtxn: &'t heed::RoTxn<'e>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
#[allow(clippy::type_complexity)]
|
||||
stack: Vec<(
|
||||
RoaringBitmap,
|
||||
std::iter::Take<heed::RoRange<'t, FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<'t, 'e> Iterator for AscendingFacetSort<'t, 'e> {
|
||||
type Item = Result<(RoaringBitmap, &'t [u8])>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
'outer: loop {
|
||||
let (documents_ids, deepest_iter) = self.stack.last_mut()?;
|
||||
for result in deepest_iter {
|
||||
let (
|
||||
FacetGroupKey { level, left_bound, field_id },
|
||||
FacetGroupValue { size: group_size, mut bitmap },
|
||||
) = result.unwrap();
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if field_id != self.field_id {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the last iterator found an empty set of documents it means
|
||||
// that we found all the documents in the sub level iterations already,
|
||||
// we can pop this level iterator.
|
||||
if documents_ids.is_empty() {
|
||||
// break our of the for loop into the end of the 'outer loop, which
|
||||
// pops the stack
|
||||
break;
|
||||
}
|
||||
|
||||
bitmap &= &*documents_ids;
|
||||
if !bitmap.is_empty() {
|
||||
*documents_ids -= &bitmap;
|
||||
|
||||
if level == 0 {
|
||||
// Since the level is 0, the left_bound is the exact value.
|
||||
return Some(Ok((bitmap, left_bound)));
|
||||
}
|
||||
let starting_key_below =
|
||||
FacetGroupKey { field_id: self.field_id, level: level - 1, left_bound };
|
||||
let iter = match self.db.range(self.rtxn, &(starting_key_below..)) {
|
||||
Ok(iter) => iter,
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
.take(group_size as usize);
|
||||
|
||||
self.stack.push((bitmap, iter));
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use crate::milli_snap;
|
||||
use crate::search::facet::facet_sort_ascending::ascending_facet_sort;
|
||||
use crate::search::facet::tests::{
|
||||
get_random_looking_index, get_random_looking_string_index_with_multiple_field_ids,
|
||||
get_simple_index, get_simple_string_index_with_multiple_field_ids,
|
||||
};
|
||||
use crate::snapshot_tests::display_bitmap;
|
||||
|
||||
#[test]
|
||||
fn filter_sort_ascending() {
|
||||
let indexes = [get_simple_index(), get_random_looking_index()];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (200..=300).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 0, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, i);
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_sort_ascending_multiple_field_ids() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (200..=300).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 0, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, format!("{i}-0"));
|
||||
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 1, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, format!("{i}-1"));
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_sort_ascending_with_no_candidates() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for index in indexes {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = RoaringBitmap::new();
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 0, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 1, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_sort_ascending_with_inexisting_field_id() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for index in indexes {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = RoaringBitmap::new();
|
||||
let mut results = String::new();
|
||||
let iter = ascending_facet_sort(&txn, index.content, 3, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
244
crates/milli/src/search/facet/facet_sort_descending.rs
Normal file
244
crates/milli/src/search/facet/facet_sort_descending.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use std::ops::Bound;
|
||||
|
||||
use heed::Result;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use super::{get_first_facet_value, get_highest_level, get_last_facet_value};
|
||||
use crate::heed_codec::facet::{
|
||||
FacetGroupKey, FacetGroupKeyCodec, FacetGroupValue, FacetGroupValueCodec,
|
||||
};
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
|
||||
/// See documentationg for [`ascending_facet_sort`](super::ascending_facet_sort).
|
||||
///
|
||||
/// This function does the same thing, but in the opposite order.
|
||||
pub fn descending_facet_sort<'t>(
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
candidates: RoaringBitmap,
|
||||
) -> Result<impl Iterator<Item = Result<(RoaringBitmap, &'t [u8])>> + 't> {
|
||||
let highest_level = get_highest_level(rtxn, db, field_id)?;
|
||||
if let Some(first_bound) = get_first_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)? {
|
||||
let first_key = FacetGroupKey { field_id, level: highest_level, left_bound: first_bound };
|
||||
let last_bound = get_last_facet_value::<BytesRefCodec, _>(rtxn, db, field_id)?.unwrap();
|
||||
let last_key = FacetGroupKey { field_id, level: highest_level, left_bound: last_bound };
|
||||
let iter = db.rev_range(rtxn, &(first_key..=last_key))?.take(usize::MAX);
|
||||
Ok(itertools::Either::Left(DescendingFacetSort {
|
||||
rtxn,
|
||||
db,
|
||||
field_id,
|
||||
stack: vec![(candidates, iter, Bound::Included(last_bound))],
|
||||
}))
|
||||
} else {
|
||||
Ok(itertools::Either::Right(std::iter::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
struct DescendingFacetSort<'t> {
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
field_id: u16,
|
||||
#[allow(clippy::type_complexity)]
|
||||
stack: Vec<(
|
||||
RoaringBitmap,
|
||||
std::iter::Take<
|
||||
heed::RoRevRange<'t, FacetGroupKeyCodec<BytesRefCodec>, FacetGroupValueCodec>,
|
||||
>,
|
||||
Bound<&'t [u8]>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<'t> Iterator for DescendingFacetSort<'t> {
|
||||
type Item = Result<(RoaringBitmap, &'t [u8])>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
'outer: loop {
|
||||
let (documents_ids, deepest_iter, right_bound) = self.stack.last_mut()?;
|
||||
for result in deepest_iter.by_ref() {
|
||||
let (
|
||||
FacetGroupKey { level, left_bound, field_id },
|
||||
FacetGroupValue { size: group_size, mut bitmap },
|
||||
) = result.unwrap();
|
||||
// The range is unbounded on the right and the group size for the highest level is MAX,
|
||||
// so we need to check that we are not iterating over the next field id
|
||||
if field_id != self.field_id {
|
||||
return None;
|
||||
}
|
||||
// If the last iterator found an empty set of documents it means
|
||||
// that we found all the documents in the sub level iterations already,
|
||||
// we can pop this level iterator.
|
||||
if documents_ids.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
bitmap &= &*documents_ids;
|
||||
if !bitmap.is_empty() {
|
||||
*documents_ids -= &bitmap;
|
||||
|
||||
if level == 0 {
|
||||
// Since we're at the level 0 the left_bound is the exact value.
|
||||
return Some(Ok((bitmap, left_bound)));
|
||||
}
|
||||
let starting_key_below =
|
||||
FacetGroupKey { field_id, level: level - 1, left_bound };
|
||||
|
||||
let end_key_kelow = match *right_bound {
|
||||
Bound::Included(right) => Bound::Included(FacetGroupKey {
|
||||
field_id,
|
||||
level: level - 1,
|
||||
left_bound: right,
|
||||
}),
|
||||
Bound::Excluded(right) => Bound::Excluded(FacetGroupKey {
|
||||
field_id,
|
||||
level: level - 1,
|
||||
left_bound: right,
|
||||
}),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
};
|
||||
let prev_right_bound = *right_bound;
|
||||
*right_bound = Bound::Excluded(left_bound);
|
||||
let iter = match self
|
||||
.db
|
||||
.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>()
|
||||
.rev_range(self.rtxn, &(Bound::Included(starting_key_below), end_key_kelow))
|
||||
{
|
||||
Ok(iter) => iter,
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
.take(group_size as usize);
|
||||
|
||||
self.stack.push((bitmap, iter, prev_right_bound));
|
||||
continue 'outer;
|
||||
}
|
||||
*right_bound = Bound::Excluded(left_bound);
|
||||
}
|
||||
self.stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use crate::heed_codec::facet::FacetGroupKeyCodec;
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
use crate::milli_snap;
|
||||
use crate::search::facet::facet_sort_descending::descending_facet_sort;
|
||||
use crate::search::facet::tests::{
|
||||
get_random_looking_index, get_random_looking_string_index_with_multiple_field_ids,
|
||||
get_simple_index, get_simple_index_with_multiple_field_ids,
|
||||
get_simple_string_index_with_multiple_field_ids,
|
||||
};
|
||||
use crate::snapshot_tests::display_bitmap;
|
||||
|
||||
#[test]
|
||||
fn filter_sort_descending() {
|
||||
let indexes = [
|
||||
get_simple_index(),
|
||||
get_random_looking_index(),
|
||||
get_simple_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (200..=300).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
let db = index.content.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>();
|
||||
let iter = descending_facet_sort(&txn, db, 0, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, i);
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_sort_descending_multiple_field_ids() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for (i, index) in indexes.iter().enumerate() {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = (200..=300).collect::<RoaringBitmap>();
|
||||
let mut results = String::new();
|
||||
let db = index.content.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>();
|
||||
let iter = descending_facet_sort(&txn, db, 0, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, format!("{i}-0"));
|
||||
|
||||
let mut results = String::new();
|
||||
|
||||
let iter = descending_facet_sort(&txn, db, 1, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
milli_snap!(results, format!("{i}-1"));
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn filter_sort_ascending_with_no_candidates() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for index in indexes {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = RoaringBitmap::new();
|
||||
let mut results = String::new();
|
||||
let iter = descending_facet_sort(&txn, index.content, 0, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
let mut results = String::new();
|
||||
let iter = descending_facet_sort(&txn, index.content, 1, candidates).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_sort_ascending_with_inexisting_field_id() {
|
||||
let indexes = [
|
||||
get_simple_string_index_with_multiple_field_ids(),
|
||||
get_random_looking_string_index_with_multiple_field_ids(),
|
||||
];
|
||||
for index in indexes {
|
||||
let txn = index.env.read_txn().unwrap();
|
||||
let candidates = RoaringBitmap::new();
|
||||
let mut results = String::new();
|
||||
let iter = descending_facet_sort(&txn, index.content, 3, candidates.clone()).unwrap();
|
||||
for el in iter {
|
||||
let (docids, _) = el.unwrap();
|
||||
results.push_str(&display_bitmap(&docids));
|
||||
results.push('\n');
|
||||
}
|
||||
assert!(results.is_empty());
|
||||
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
1211
crates/milli/src/search/facet/filter.rs
Normal file
1211
crates/milli/src/search/facet/filter.rs
Normal file
File diff suppressed because it is too large
Load Diff
227
crates/milli/src/search/facet/mod.rs
Normal file
227
crates/milli/src/search/facet/mod.rs
Normal file
@ -0,0 +1,227 @@
|
||||
pub use facet_sort_ascending::ascending_facet_sort;
|
||||
pub use facet_sort_descending::descending_facet_sort;
|
||||
use heed::types::{Bytes, DecodeIgnore};
|
||||
use heed::{BytesDecode, RoTxn};
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
pub use self::facet_distribution::{FacetDistribution, OrderBy, DEFAULT_VALUES_PER_FACET};
|
||||
pub use self::filter::{BadGeoError, Filter};
|
||||
pub use self::search::{FacetValueHit, SearchForFacetValues};
|
||||
use crate::heed_codec::facet::{FacetGroupKeyCodec, OrderedF64Codec};
|
||||
use crate::heed_codec::BytesRefCodec;
|
||||
use crate::{Index, Result};
|
||||
|
||||
mod facet_distribution;
|
||||
mod facet_distribution_iter;
|
||||
mod facet_range_search;
|
||||
mod facet_sort_ascending;
|
||||
mod facet_sort_descending;
|
||||
mod filter;
|
||||
mod search;
|
||||
|
||||
fn facet_extreme_value<'t>(
|
||||
mut extreme_it: impl Iterator<Item = heed::Result<(RoaringBitmap, &'t [u8])>> + 't,
|
||||
) -> Result<Option<f64>> {
|
||||
let extreme_value =
|
||||
if let Some(extreme_value) = extreme_it.next() { extreme_value } else { return Ok(None) };
|
||||
let (_, extreme_value) = extreme_value?;
|
||||
OrderedF64Codec::bytes_decode(extreme_value)
|
||||
.map(Some)
|
||||
.map_err(heed::Error::Decoding)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn facet_min_value<'t>(
|
||||
index: &'t Index,
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
field_id: u16,
|
||||
candidates: RoaringBitmap,
|
||||
) -> Result<Option<f64>> {
|
||||
let db = index.facet_id_f64_docids.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>();
|
||||
let it = ascending_facet_sort(rtxn, db, field_id, candidates)?;
|
||||
facet_extreme_value(it)
|
||||
}
|
||||
|
||||
pub fn facet_max_value<'t>(
|
||||
index: &'t Index,
|
||||
rtxn: &'t heed::RoTxn<'t>,
|
||||
field_id: u16,
|
||||
candidates: RoaringBitmap,
|
||||
) -> Result<Option<f64>> {
|
||||
let db = index.facet_id_f64_docids.remap_key_type::<FacetGroupKeyCodec<BytesRefCodec>>();
|
||||
let it = descending_facet_sort(rtxn, db, field_id, candidates)?;
|
||||
facet_extreme_value(it)
|
||||
}
|
||||
|
||||
/// Get the first facet value in the facet database
|
||||
pub(crate) fn get_first_facet_value<'t, BoundCodec, DC>(
|
||||
txn: &'t RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, DC>,
|
||||
field_id: u16,
|
||||
) -> heed::Result<Option<BoundCodec::DItem>>
|
||||
where
|
||||
BoundCodec: BytesDecode<'t>,
|
||||
{
|
||||
let mut level0prefix = vec![];
|
||||
level0prefix.extend_from_slice(&field_id.to_be_bytes());
|
||||
level0prefix.push(0);
|
||||
let mut level0_iter_forward =
|
||||
db.remap_types::<Bytes, DecodeIgnore>().prefix_iter(txn, level0prefix.as_slice())?;
|
||||
if let Some(first) = level0_iter_forward.next() {
|
||||
let (first_key, _) = first?;
|
||||
let first_key = FacetGroupKeyCodec::<BoundCodec>::bytes_decode(first_key)
|
||||
.map_err(heed::Error::Decoding)?;
|
||||
Ok(Some(first_key.left_bound))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last facet value in the facet database
|
||||
pub(crate) fn get_last_facet_value<'t, BoundCodec, DC>(
|
||||
txn: &'t RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, DC>,
|
||||
field_id: u16,
|
||||
) -> heed::Result<Option<BoundCodec::DItem>>
|
||||
where
|
||||
BoundCodec: BytesDecode<'t>,
|
||||
{
|
||||
let mut level0prefix = vec![];
|
||||
level0prefix.extend_from_slice(&field_id.to_be_bytes());
|
||||
level0prefix.push(0);
|
||||
let mut level0_iter_backward =
|
||||
db.remap_types::<Bytes, DecodeIgnore>().rev_prefix_iter(txn, level0prefix.as_slice())?;
|
||||
if let Some(last) = level0_iter_backward.next() {
|
||||
let (last_key, _) = last?;
|
||||
let last_key = FacetGroupKeyCodec::<BoundCodec>::bytes_decode(last_key)
|
||||
.map_err(heed::Error::Decoding)?;
|
||||
Ok(Some(last_key.left_bound))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the height of the highest level in the facet database
|
||||
pub(crate) fn get_highest_level<'t, DC>(
|
||||
txn: &'t RoTxn<'t>,
|
||||
db: heed::Database<FacetGroupKeyCodec<BytesRefCodec>, DC>,
|
||||
field_id: u16,
|
||||
) -> heed::Result<u8> {
|
||||
let field_id_prefix = &field_id.to_be_bytes();
|
||||
Ok(db
|
||||
.remap_types::<Bytes, DecodeIgnore>()
|
||||
.rev_prefix_iter(txn, field_id_prefix)?
|
||||
.next()
|
||||
.map(|el| {
|
||||
let (key, _) = el.unwrap();
|
||||
let key = FacetGroupKeyCodec::<BytesRefCodec>::bytes_decode(key).unwrap();
|
||||
key.level
|
||||
})
|
||||
.unwrap_or(0))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use rand::{Rng, SeedableRng};
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use crate::heed_codec::facet::OrderedF64Codec;
|
||||
use crate::heed_codec::StrRefCodec;
|
||||
use crate::update::facet::test_helpers::FacetIndex;
|
||||
|
||||
pub fn get_simple_index() -> FacetIndex<OrderedF64Codec> {
|
||||
let index = FacetIndex::<OrderedF64Codec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
for i in 0..256u16 {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(i as u32);
|
||||
index.insert(&mut txn, 0, &(i as f64), &bitmap);
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
pub fn get_random_looking_index() -> FacetIndex<OrderedF64Codec> {
|
||||
let index = FacetIndex::<OrderedF64Codec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||
|
||||
for key in std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128) {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(key);
|
||||
bitmap.insert(key + 100);
|
||||
index.insert(&mut txn, 0, &(key as f64), &bitmap);
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
pub fn get_simple_index_with_multiple_field_ids() -> FacetIndex<OrderedF64Codec> {
|
||||
let index = FacetIndex::<OrderedF64Codec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
for fid in 0..2 {
|
||||
for i in 0..256u16 {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(i as u32);
|
||||
index.insert(&mut txn, fid, &(i as f64), &bitmap);
|
||||
}
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
pub fn get_random_looking_index_with_multiple_field_ids() -> FacetIndex<OrderedF64Codec> {
|
||||
let index = FacetIndex::<OrderedF64Codec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
|
||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||
let keys =
|
||||
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
||||
for fid in 0..2 {
|
||||
for &key in &keys {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(key);
|
||||
bitmap.insert(key + 100);
|
||||
index.insert(&mut txn, fid, &(key as f64), &bitmap);
|
||||
}
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
pub fn get_simple_string_index_with_multiple_field_ids() -> FacetIndex<StrRefCodec> {
|
||||
let index = FacetIndex::<StrRefCodec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
for fid in 0..2 {
|
||||
for i in 0..256u16 {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(i as u32);
|
||||
if i % 2 == 0 {
|
||||
index.insert(&mut txn, fid, &format!("{i}").as_str(), &bitmap);
|
||||
} else {
|
||||
index.insert(&mut txn, fid, &"", &bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
pub fn get_random_looking_string_index_with_multiple_field_ids() -> FacetIndex<StrRefCodec> {
|
||||
let index = FacetIndex::<StrRefCodec>::new(4, 8, 5);
|
||||
let mut txn = index.env.write_txn().unwrap();
|
||||
|
||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||
let keys =
|
||||
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
||||
for fid in 0..2 {
|
||||
for &key in &keys {
|
||||
let mut bitmap = RoaringBitmap::new();
|
||||
bitmap.insert(key);
|
||||
bitmap.insert(key + 100);
|
||||
if key % 2 == 0 {
|
||||
index.insert(&mut txn, fid, &format!("{key}").as_str(), &bitmap);
|
||||
} else {
|
||||
index.insert(&mut txn, fid, &"", &bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
index
|
||||
}
|
||||
}
|
358
crates/milli/src/search/facet/search.rs
Normal file
358
crates/milli/src/search/facet/search.rs
Normal file
@ -0,0 +1,358 @@
|
||||
use std::cmp::{Ordering, Reverse};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use charabia::normalizer::NormalizerOption;
|
||||
use charabia::{Language, Normalize, StrDetection, Token};
|
||||
use fst::automaton::{Automaton, Str};
|
||||
use fst::{IntoStreamer, Streamer};
|
||||
use roaring::RoaringBitmap;
|
||||
use tracing::error;
|
||||
|
||||
use crate::error::UserError;
|
||||
use crate::heed_codec::facet::{FacetGroupKey, FacetGroupValue};
|
||||
use crate::search::build_dfa;
|
||||
use crate::{DocumentId, FieldId, OrderBy, Result, Search};
|
||||
|
||||
/// The maximum number of values per facet returned by the facet search route.
|
||||
const DEFAULT_MAX_NUMBER_OF_VALUES_PER_FACET: usize = 100;
|
||||
|
||||
pub struct SearchForFacetValues<'a> {
|
||||
query: Option<String>,
|
||||
facet: String,
|
||||
search_query: Search<'a>,
|
||||
max_values: usize,
|
||||
is_hybrid: bool,
|
||||
locales: Option<Vec<Language>>,
|
||||
}
|
||||
|
||||
impl<'a> SearchForFacetValues<'a> {
|
||||
pub fn new(
|
||||
facet: String,
|
||||
search_query: Search<'a>,
|
||||
is_hybrid: bool,
|
||||
) -> SearchForFacetValues<'a> {
|
||||
SearchForFacetValues {
|
||||
query: None,
|
||||
facet,
|
||||
search_query,
|
||||
max_values: DEFAULT_MAX_NUMBER_OF_VALUES_PER_FACET,
|
||||
is_hybrid,
|
||||
locales: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(&mut self, query: impl Into<String>) -> &mut Self {
|
||||
self.query = Some(query.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_values(&mut self, max: usize) -> &mut Self {
|
||||
self.max_values = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn locales(&mut self, locales: Vec<Language>) -> &mut Self {
|
||||
self.locales = Some(locales);
|
||||
self
|
||||
}
|
||||
|
||||
fn one_original_value_of(
|
||||
&self,
|
||||
field_id: FieldId,
|
||||
facet_str: &str,
|
||||
any_docid: DocumentId,
|
||||
) -> Result<Option<String>> {
|
||||
let index = self.search_query.index;
|
||||
let rtxn = self.search_query.rtxn;
|
||||
let key: (FieldId, _, &str) = (field_id, any_docid, facet_str);
|
||||
Ok(index.field_id_docid_facet_strings.get(rtxn, &key)?.map(|v| v.to_owned()))
|
||||
}
|
||||
|
||||
pub fn execute(&self) -> Result<Vec<FacetValueHit>> {
|
||||
let index = self.search_query.index;
|
||||
let rtxn = self.search_query.rtxn;
|
||||
|
||||
let filterable_fields = index.filterable_fields(rtxn)?;
|
||||
if !filterable_fields.contains(&self.facet) {
|
||||
let (valid_fields, hidden_fields) =
|
||||
index.remove_hidden_fields(rtxn, filterable_fields)?;
|
||||
|
||||
return Err(UserError::InvalidFacetSearchFacetName {
|
||||
field: self.facet.clone(),
|
||||
valid_fields,
|
||||
hidden_fields,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let fields_ids_map = index.fields_ids_map(rtxn)?;
|
||||
let fid = match fields_ids_map.id(&self.facet) {
|
||||
Some(fid) => fid,
|
||||
// we return an empty list of results when the attribute has been
|
||||
// set as filterable but no document contains this field (yet).
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let fst = match self.search_query.index.facet_id_string_fst.get(rtxn, &fid)? {
|
||||
Some(fst) => fst,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let search_candidates = self.search_query.execute_for_candidates(
|
||||
self.is_hybrid
|
||||
|| self
|
||||
.search_query
|
||||
.semantic
|
||||
.as_ref()
|
||||
.and_then(|semantic| semantic.vector.as_ref())
|
||||
.is_some(),
|
||||
)?;
|
||||
|
||||
let mut results = match index.sort_facet_values_by(rtxn)?.get(&self.facet) {
|
||||
OrderBy::Lexicographic => ValuesCollection::by_lexicographic(self.max_values),
|
||||
OrderBy::Count => ValuesCollection::by_count(self.max_values),
|
||||
};
|
||||
|
||||
match self.query.as_ref() {
|
||||
Some(query) => {
|
||||
let query = normalize_facet_string(query, self.locales.as_deref());
|
||||
let query = query.as_ref();
|
||||
|
||||
let authorize_typos = self.search_query.index.authorize_typos(rtxn)?;
|
||||
let field_authorizes_typos =
|
||||
!self.search_query.index.exact_attributes_ids(rtxn)?.contains(&fid);
|
||||
|
||||
if authorize_typos && field_authorizes_typos {
|
||||
let exact_words_fst = self.search_query.index.exact_words(rtxn)?;
|
||||
if exact_words_fst.map_or(false, |fst| fst.contains(query)) {
|
||||
if fst.contains(query) {
|
||||
self.fetch_original_facets_using_normalized(
|
||||
fid,
|
||||
query,
|
||||
query,
|
||||
&search_candidates,
|
||||
&mut results,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
let one_typo = self.search_query.index.min_word_len_one_typo(rtxn)?;
|
||||
let two_typos = self.search_query.index.min_word_len_two_typos(rtxn)?;
|
||||
|
||||
let is_prefix = true;
|
||||
let automaton = if query.len() < one_typo as usize {
|
||||
build_dfa(query, 0, is_prefix)
|
||||
} else if query.len() < two_typos as usize {
|
||||
build_dfa(query, 1, is_prefix)
|
||||
} else {
|
||||
build_dfa(query, 2, is_prefix)
|
||||
};
|
||||
|
||||
let mut stream = fst.search(automaton).into_stream();
|
||||
while let Some(facet_value) = stream.next() {
|
||||
let value = std::str::from_utf8(facet_value)?;
|
||||
if self
|
||||
.fetch_original_facets_using_normalized(
|
||||
fid,
|
||||
value,
|
||||
query,
|
||||
&search_candidates,
|
||||
&mut results,
|
||||
)?
|
||||
.is_break()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let automaton = Str::new(query).starts_with();
|
||||
let mut stream = fst.search(automaton).into_stream();
|
||||
while let Some(facet_value) = stream.next() {
|
||||
let value = std::str::from_utf8(facet_value)?;
|
||||
if self
|
||||
.fetch_original_facets_using_normalized(
|
||||
fid,
|
||||
value,
|
||||
query,
|
||||
&search_candidates,
|
||||
&mut results,
|
||||
)?
|
||||
.is_break()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let prefix = FacetGroupKey { field_id: fid, level: 0, left_bound: "" };
|
||||
for result in index.facet_id_string_docids.prefix_iter(rtxn, &prefix)? {
|
||||
let (FacetGroupKey { left_bound, .. }, FacetGroupValue { bitmap, .. }) =
|
||||
result?;
|
||||
let count = search_candidates.intersection_len(&bitmap);
|
||||
if count != 0 {
|
||||
let value = self
|
||||
.one_original_value_of(fid, left_bound, bitmap.min().unwrap())?
|
||||
.unwrap_or_else(|| left_bound.to_string());
|
||||
if results.insert(FacetValueHit { value, count }).is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results.into_sorted_vec())
|
||||
}
|
||||
|
||||
fn fetch_original_facets_using_normalized(
|
||||
&self,
|
||||
fid: FieldId,
|
||||
value: &str,
|
||||
query: &str,
|
||||
search_candidates: &RoaringBitmap,
|
||||
results: &mut ValuesCollection,
|
||||
) -> Result<ControlFlow<()>> {
|
||||
let index = self.search_query.index;
|
||||
let rtxn = self.search_query.rtxn;
|
||||
|
||||
let database = index.facet_id_normalized_string_strings;
|
||||
let key = (fid, value);
|
||||
let original_strings = match database.get(rtxn, &key)? {
|
||||
Some(original_strings) => original_strings,
|
||||
None => {
|
||||
error!("the facet value is missing from the facet database: {key:?}");
|
||||
return Ok(ControlFlow::Continue(()));
|
||||
}
|
||||
};
|
||||
for original in original_strings {
|
||||
let key = FacetGroupKey { field_id: fid, level: 0, left_bound: original.as_str() };
|
||||
let docids = match index.facet_id_string_docids.get(rtxn, &key)? {
|
||||
Some(FacetGroupValue { bitmap, .. }) => bitmap,
|
||||
None => {
|
||||
error!("the facet value is missing from the facet database: {key:?}");
|
||||
return Ok(ControlFlow::Continue(()));
|
||||
}
|
||||
};
|
||||
let count = search_candidates.intersection_len(&docids);
|
||||
if count != 0 {
|
||||
let value = self
|
||||
.one_original_value_of(fid, &original, docids.min().unwrap())?
|
||||
.unwrap_or_else(|| query.to_string());
|
||||
if results.insert(FacetValueHit { value, count }).is_break() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, PartialEq)]
|
||||
pub struct FacetValueHit {
|
||||
/// The original facet value
|
||||
pub value: String,
|
||||
/// The number of documents associated to this facet
|
||||
pub count: u64,
|
||||
}
|
||||
|
||||
impl PartialOrd for FacetValueHit {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FacetValueHit {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.count.cmp(&other.count).then_with(|| self.value.cmp(&other.value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FacetValueHit {}
|
||||
|
||||
/// A wrapper type that collects the best facet values by
|
||||
/// lexicographic or number of associated values.
|
||||
enum ValuesCollection {
|
||||
/// Keeps the top values according to the lexicographic order.
|
||||
Lexicographic { max: usize, content: Vec<FacetValueHit> },
|
||||
/// Keeps the top values according to the number of values associated to them.
|
||||
///
|
||||
/// Note that it is a max heap and we need to move the smallest counts
|
||||
/// at the top to be able to pop them when we reach the max_values limit.
|
||||
Count { max: usize, content: BinaryHeap<Reverse<FacetValueHit>> },
|
||||
}
|
||||
|
||||
impl ValuesCollection {
|
||||
pub fn by_lexicographic(max: usize) -> Self {
|
||||
ValuesCollection::Lexicographic { max, content: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn by_count(max: usize) -> Self {
|
||||
ValuesCollection::Count { max, content: BinaryHeap::new() }
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: FacetValueHit) -> ControlFlow<()> {
|
||||
match self {
|
||||
ValuesCollection::Lexicographic { max, content } => {
|
||||
if content.len() < *max {
|
||||
content.push(value);
|
||||
if content.len() < *max {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
}
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
ValuesCollection::Count { max, content } => {
|
||||
if content.len() == *max {
|
||||
// Peeking gives us the worst value in the list as
|
||||
// this is a max-heap and we reversed it.
|
||||
let Some(mut peek) = content.peek_mut() else { return ControlFlow::Break(()) };
|
||||
if peek.0.count <= value.count {
|
||||
// Replace the current worst value in the heap
|
||||
// with the new one we received that is better.
|
||||
*peek = Reverse(value);
|
||||
}
|
||||
} else {
|
||||
content.push(Reverse(value));
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of facet values in descending order of, either,
|
||||
/// count or lexicographic order of the value depending on the type.
|
||||
pub fn into_sorted_vec(self) -> Vec<FacetValueHit> {
|
||||
match self {
|
||||
ValuesCollection::Lexicographic { content, .. } => content.into_iter().collect(),
|
||||
ValuesCollection::Count { content, .. } => {
|
||||
// Convert the heap into a vec of hits by removing the Reverse wrapper.
|
||||
// Hits are already in the right order as they were reversed and there
|
||||
// are output in ascending order.
|
||||
content.into_sorted_vec().into_iter().map(|Reverse(hit)| hit).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn normalize_facet_string(facet_string: &str, locales: Option<&[Language]>) -> String {
|
||||
let options = NormalizerOption { lossy: true, ..Default::default() };
|
||||
let mut detection = StrDetection::new(facet_string, locales);
|
||||
|
||||
// Detect the language of the facet string only if several locales are explicitly provided.
|
||||
let language = match locales {
|
||||
Some(&[language]) => Some(language),
|
||||
Some(multiple_locales) if multiple_locales.len() > 1 => detection.language(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let token = Token {
|
||||
lemma: std::borrow::Cow::Borrowed(facet_string),
|
||||
script: detection.script(),
|
||||
language,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
token.normalize(&options).lemma.into_owned()
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_distribution_iter.rs
|
||||
---
|
||||
0: 1
|
||||
1: 1
|
||||
2: 1
|
||||
3: 1
|
||||
4: 1
|
||||
5: 1
|
||||
6: 1
|
||||
7: 1
|
||||
8: 1
|
||||
9: 1
|
||||
10: 1
|
||||
11: 1
|
||||
12: 1
|
||||
13: 1
|
||||
14: 1
|
||||
15: 1
|
||||
16: 1
|
||||
17: 1
|
||||
18: 1
|
||||
19: 1
|
||||
20: 1
|
||||
21: 1
|
||||
22: 1
|
||||
23: 1
|
||||
24: 1
|
||||
25: 1
|
||||
26: 1
|
||||
27: 1
|
||||
28: 1
|
||||
29: 1
|
||||
30: 1
|
||||
31: 1
|
||||
32: 1
|
||||
33: 1
|
||||
34: 1
|
||||
35: 1
|
||||
36: 1
|
||||
37: 1
|
||||
38: 1
|
||||
39: 1
|
||||
40: 1
|
||||
41: 1
|
||||
42: 1
|
||||
43: 1
|
||||
44: 1
|
||||
45: 1
|
||||
46: 1
|
||||
47: 1
|
||||
48: 1
|
||||
49: 1
|
||||
50: 1
|
||||
51: 1
|
||||
52: 1
|
||||
53: 1
|
||||
54: 1
|
||||
55: 1
|
||||
56: 1
|
||||
57: 1
|
||||
58: 1
|
||||
59: 1
|
||||
60: 1
|
||||
61: 1
|
||||
62: 1
|
||||
63: 1
|
||||
64: 1
|
||||
65: 1
|
||||
66: 1
|
||||
67: 1
|
||||
68: 1
|
||||
69: 1
|
||||
70: 1
|
||||
71: 1
|
||||
72: 1
|
||||
73: 1
|
||||
74: 1
|
||||
75: 1
|
||||
76: 1
|
||||
77: 1
|
||||
78: 1
|
||||
79: 1
|
||||
80: 1
|
||||
81: 1
|
||||
82: 1
|
||||
83: 1
|
||||
84: 1
|
||||
85: 1
|
||||
86: 1
|
||||
87: 1
|
||||
88: 1
|
||||
89: 1
|
||||
90: 1
|
||||
91: 1
|
||||
92: 1
|
||||
93: 1
|
||||
94: 1
|
||||
95: 1
|
||||
96: 1
|
||||
97: 1
|
||||
98: 1
|
||||
99: 1
|
||||
100: 1
|
||||
101: 1
|
||||
102: 1
|
||||
103: 1
|
||||
104: 1
|
||||
105: 1
|
||||
106: 1
|
||||
107: 1
|
||||
108: 1
|
||||
109: 1
|
||||
110: 1
|
||||
111: 1
|
||||
112: 1
|
||||
113: 1
|
||||
114: 1
|
||||
115: 1
|
||||
116: 1
|
||||
117: 1
|
||||
118: 1
|
||||
119: 1
|
||||
120: 1
|
||||
121: 1
|
||||
122: 1
|
||||
123: 1
|
||||
124: 1
|
||||
125: 1
|
||||
126: 1
|
||||
127: 1
|
||||
128: 1
|
||||
129: 1
|
||||
130: 1
|
||||
131: 1
|
||||
132: 1
|
||||
133: 1
|
||||
134: 1
|
||||
135: 1
|
||||
136: 1
|
||||
137: 1
|
||||
138: 1
|
||||
139: 1
|
||||
140: 1
|
||||
141: 1
|
||||
142: 1
|
||||
143: 1
|
||||
144: 1
|
||||
145: 1
|
||||
146: 1
|
||||
147: 1
|
||||
148: 1
|
||||
149: 1
|
||||
150: 1
|
||||
151: 1
|
||||
152: 1
|
||||
153: 1
|
||||
154: 1
|
||||
155: 1
|
||||
156: 1
|
||||
157: 1
|
||||
158: 1
|
||||
159: 1
|
||||
160: 1
|
||||
161: 1
|
||||
162: 1
|
||||
163: 1
|
||||
164: 1
|
||||
165: 1
|
||||
166: 1
|
||||
167: 1
|
||||
168: 1
|
||||
169: 1
|
||||
170: 1
|
||||
171: 1
|
||||
172: 1
|
||||
173: 1
|
||||
174: 1
|
||||
175: 1
|
||||
176: 1
|
||||
177: 1
|
||||
178: 1
|
||||
179: 1
|
||||
180: 1
|
||||
181: 1
|
||||
182: 1
|
||||
183: 1
|
||||
184: 1
|
||||
185: 1
|
||||
186: 1
|
||||
187: 1
|
||||
188: 1
|
||||
189: 1
|
||||
190: 1
|
||||
191: 1
|
||||
192: 1
|
||||
193: 1
|
||||
194: 1
|
||||
195: 1
|
||||
196: 1
|
||||
197: 1
|
||||
198: 1
|
||||
199: 1
|
||||
200: 1
|
||||
201: 1
|
||||
202: 1
|
||||
203: 1
|
||||
204: 1
|
||||
205: 1
|
||||
206: 1
|
||||
207: 1
|
||||
208: 1
|
||||
209: 1
|
||||
210: 1
|
||||
211: 1
|
||||
212: 1
|
||||
213: 1
|
||||
214: 1
|
||||
215: 1
|
||||
216: 1
|
||||
217: 1
|
||||
218: 1
|
||||
219: 1
|
||||
220: 1
|
||||
221: 1
|
||||
222: 1
|
||||
223: 1
|
||||
224: 1
|
||||
225: 1
|
||||
226: 1
|
||||
227: 1
|
||||
228: 1
|
||||
229: 1
|
||||
230: 1
|
||||
231: 1
|
||||
232: 1
|
||||
233: 1
|
||||
234: 1
|
||||
235: 1
|
||||
236: 1
|
||||
237: 1
|
||||
238: 1
|
||||
239: 1
|
||||
240: 1
|
||||
241: 1
|
||||
242: 1
|
||||
243: 1
|
||||
244: 1
|
||||
245: 1
|
||||
246: 1
|
||||
247: 1
|
||||
248: 1
|
||||
249: 1
|
||||
250: 1
|
||||
251: 1
|
||||
252: 1
|
||||
253: 1
|
||||
254: 1
|
||||
255: 1
|
||||
|
@ -0,0 +1,105 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_distribution_iter.rs
|
||||
---
|
||||
3: 2
|
||||
5: 2
|
||||
6: 2
|
||||
9: 2
|
||||
10: 2
|
||||
11: 2
|
||||
14: 2
|
||||
18: 2
|
||||
19: 2
|
||||
24: 2
|
||||
26: 2
|
||||
28: 2
|
||||
29: 2
|
||||
32: 2
|
||||
33: 2
|
||||
35: 2
|
||||
36: 2
|
||||
37: 2
|
||||
38: 2
|
||||
39: 2
|
||||
41: 2
|
||||
46: 2
|
||||
47: 2
|
||||
49: 2
|
||||
52: 2
|
||||
53: 2
|
||||
55: 2
|
||||
59: 2
|
||||
61: 2
|
||||
64: 2
|
||||
68: 2
|
||||
71: 2
|
||||
74: 2
|
||||
75: 2
|
||||
76: 2
|
||||
81: 2
|
||||
83: 2
|
||||
85: 2
|
||||
86: 2
|
||||
88: 2
|
||||
90: 2
|
||||
91: 2
|
||||
92: 2
|
||||
98: 2
|
||||
99: 2
|
||||
101: 2
|
||||
102: 2
|
||||
103: 2
|
||||
107: 2
|
||||
111: 2
|
||||
115: 2
|
||||
119: 2
|
||||
123: 2
|
||||
124: 2
|
||||
130: 2
|
||||
131: 2
|
||||
133: 2
|
||||
135: 2
|
||||
136: 2
|
||||
137: 2
|
||||
139: 2
|
||||
141: 2
|
||||
143: 2
|
||||
144: 2
|
||||
147: 2
|
||||
150: 2
|
||||
156: 1
|
||||
158: 1
|
||||
160: 1
|
||||
162: 1
|
||||
163: 1
|
||||
164: 1
|
||||
167: 1
|
||||
169: 1
|
||||
173: 1
|
||||
177: 1
|
||||
178: 1
|
||||
179: 1
|
||||
181: 1
|
||||
182: 1
|
||||
186: 1
|
||||
189: 1
|
||||
192: 1
|
||||
193: 1
|
||||
195: 1
|
||||
197: 1
|
||||
205: 1
|
||||
206: 1
|
||||
207: 1
|
||||
208: 1
|
||||
209: 1
|
||||
210: 1
|
||||
216: 1
|
||||
219: 1
|
||||
220: 1
|
||||
223: 1
|
||||
226: 1
|
||||
235: 1
|
||||
236: 1
|
||||
238: 1
|
||||
243: 1
|
||||
|
@ -0,0 +1,104 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_distribution_iter.rs
|
||||
---
|
||||
0: 1
|
||||
1: 1
|
||||
2: 1
|
||||
3: 1
|
||||
4: 1
|
||||
5: 1
|
||||
6: 1
|
||||
7: 1
|
||||
8: 1
|
||||
9: 1
|
||||
10: 1
|
||||
11: 1
|
||||
12: 1
|
||||
13: 1
|
||||
14: 1
|
||||
15: 1
|
||||
16: 1
|
||||
17: 1
|
||||
18: 1
|
||||
19: 1
|
||||
20: 1
|
||||
21: 1
|
||||
22: 1
|
||||
23: 1
|
||||
24: 1
|
||||
25: 1
|
||||
26: 1
|
||||
27: 1
|
||||
28: 1
|
||||
29: 1
|
||||
30: 1
|
||||
31: 1
|
||||
32: 1
|
||||
33: 1
|
||||
34: 1
|
||||
35: 1
|
||||
36: 1
|
||||
37: 1
|
||||
38: 1
|
||||
39: 1
|
||||
40: 1
|
||||
41: 1
|
||||
42: 1
|
||||
43: 1
|
||||
44: 1
|
||||
45: 1
|
||||
46: 1
|
||||
47: 1
|
||||
48: 1
|
||||
49: 1
|
||||
50: 1
|
||||
51: 1
|
||||
52: 1
|
||||
53: 1
|
||||
54: 1
|
||||
55: 1
|
||||
56: 1
|
||||
57: 1
|
||||
58: 1
|
||||
59: 1
|
||||
60: 1
|
||||
61: 1
|
||||
62: 1
|
||||
63: 1
|
||||
64: 1
|
||||
65: 1
|
||||
66: 1
|
||||
67: 1
|
||||
68: 1
|
||||
69: 1
|
||||
70: 1
|
||||
71: 1
|
||||
72: 1
|
||||
73: 1
|
||||
74: 1
|
||||
75: 1
|
||||
76: 1
|
||||
77: 1
|
||||
78: 1
|
||||
79: 1
|
||||
80: 1
|
||||
81: 1
|
||||
82: 1
|
||||
83: 1
|
||||
84: 1
|
||||
85: 1
|
||||
86: 1
|
||||
87: 1
|
||||
88: 1
|
||||
89: 1
|
||||
90: 1
|
||||
91: 1
|
||||
92: 1
|
||||
93: 1
|
||||
94: 1
|
||||
95: 1
|
||||
96: 1
|
||||
97: 1
|
||||
98: 1
|
||||
99: 1
|
||||
|
@ -0,0 +1,104 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_distribution_iter.rs
|
||||
---
|
||||
3: 2
|
||||
5: 2
|
||||
6: 2
|
||||
9: 2
|
||||
10: 2
|
||||
11: 2
|
||||
14: 2
|
||||
18: 2
|
||||
19: 2
|
||||
24: 2
|
||||
26: 2
|
||||
28: 2
|
||||
29: 2
|
||||
32: 2
|
||||
33: 2
|
||||
35: 2
|
||||
36: 2
|
||||
37: 2
|
||||
38: 2
|
||||
39: 2
|
||||
41: 2
|
||||
46: 2
|
||||
47: 2
|
||||
49: 2
|
||||
52: 2
|
||||
53: 2
|
||||
55: 2
|
||||
59: 2
|
||||
61: 2
|
||||
64: 2
|
||||
68: 2
|
||||
71: 2
|
||||
74: 2
|
||||
75: 2
|
||||
76: 2
|
||||
81: 2
|
||||
83: 2
|
||||
85: 2
|
||||
86: 2
|
||||
88: 2
|
||||
90: 2
|
||||
91: 2
|
||||
92: 2
|
||||
98: 2
|
||||
99: 2
|
||||
101: 2
|
||||
102: 2
|
||||
103: 2
|
||||
107: 2
|
||||
111: 2
|
||||
115: 2
|
||||
119: 2
|
||||
123: 2
|
||||
124: 2
|
||||
130: 2
|
||||
131: 2
|
||||
133: 2
|
||||
135: 2
|
||||
136: 2
|
||||
137: 2
|
||||
139: 2
|
||||
141: 2
|
||||
143: 2
|
||||
144: 2
|
||||
147: 2
|
||||
150: 2
|
||||
156: 1
|
||||
158: 1
|
||||
160: 1
|
||||
162: 1
|
||||
163: 1
|
||||
164: 1
|
||||
167: 1
|
||||
169: 1
|
||||
173: 1
|
||||
177: 1
|
||||
178: 1
|
||||
179: 1
|
||||
181: 1
|
||||
182: 1
|
||||
186: 1
|
||||
189: 1
|
||||
192: 1
|
||||
193: 1
|
||||
195: 1
|
||||
197: 1
|
||||
205: 1
|
||||
206: 1
|
||||
207: 1
|
||||
208: 1
|
||||
209: 1
|
||||
210: 1
|
||||
216: 1
|
||||
219: 1
|
||||
220: 1
|
||||
223: 1
|
||||
226: 1
|
||||
235: 1
|
||||
236: 1
|
||||
238: 1
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
adf484f467a31ee9460dec539621938a
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
c9939aa4977fcd4bfd35852e102dbc82
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
adf484f467a31ee9460dec539621938a
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
c9939aa4977fcd4bfd35852e102dbc82
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
618738d28ff1386b6e93d171a5acb08f
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
ffb62ab3eef55c2254c13dc0f4099849
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
618738d28ff1386b6e93d171a5acb08f
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
ffb62ab3eef55c2254c13dc0f4099849
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
9c25261cec7275cb5cfd85835904d023
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
2f97f18c15e915853e4df879be6e1f63
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
9c25261cec7275cb5cfd85835904d023
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
2f97f18c15e915853e4df879be6e1f63
|
@ -0,0 +1,260 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
0: []
|
||||
1: []
|
||||
2: []
|
||||
3: []
|
||||
4: []
|
||||
5: []
|
||||
6: []
|
||||
7: []
|
||||
8: []
|
||||
9: []
|
||||
10: []
|
||||
11: []
|
||||
12: []
|
||||
13: []
|
||||
14: []
|
||||
15: []
|
||||
16: []
|
||||
17: []
|
||||
18: []
|
||||
19: []
|
||||
20: []
|
||||
21: []
|
||||
22: []
|
||||
23: []
|
||||
24: []
|
||||
25: []
|
||||
26: []
|
||||
27: []
|
||||
28: []
|
||||
29: []
|
||||
30: []
|
||||
31: []
|
||||
32: []
|
||||
33: []
|
||||
34: []
|
||||
35: []
|
||||
36: []
|
||||
37: []
|
||||
38: []
|
||||
39: []
|
||||
40: []
|
||||
41: []
|
||||
42: []
|
||||
43: []
|
||||
44: []
|
||||
45: []
|
||||
46: []
|
||||
47: []
|
||||
48: []
|
||||
49: []
|
||||
50: []
|
||||
51: []
|
||||
52: []
|
||||
53: []
|
||||
54: []
|
||||
55: []
|
||||
56: []
|
||||
57: []
|
||||
58: []
|
||||
59: []
|
||||
60: []
|
||||
61: []
|
||||
62: []
|
||||
63: []
|
||||
64: []
|
||||
65: []
|
||||
66: []
|
||||
67: []
|
||||
68: []
|
||||
69: []
|
||||
70: []
|
||||
71: []
|
||||
72: []
|
||||
73: []
|
||||
74: []
|
||||
75: []
|
||||
76: []
|
||||
77: []
|
||||
78: []
|
||||
79: []
|
||||
80: []
|
||||
81: []
|
||||
82: []
|
||||
83: []
|
||||
84: []
|
||||
85: []
|
||||
86: []
|
||||
87: []
|
||||
88: []
|
||||
89: []
|
||||
90: []
|
||||
91: []
|
||||
92: []
|
||||
93: []
|
||||
94: []
|
||||
95: []
|
||||
96: []
|
||||
97: []
|
||||
98: []
|
||||
99: []
|
||||
100: []
|
||||
101: []
|
||||
102: []
|
||||
103: []
|
||||
104: []
|
||||
105: []
|
||||
106: []
|
||||
107: []
|
||||
108: []
|
||||
109: []
|
||||
110: []
|
||||
111: []
|
||||
112: []
|
||||
113: []
|
||||
114: []
|
||||
115: []
|
||||
116: []
|
||||
117: []
|
||||
118: []
|
||||
119: []
|
||||
120: []
|
||||
121: []
|
||||
122: []
|
||||
123: []
|
||||
124: []
|
||||
125: []
|
||||
126: []
|
||||
127: []
|
||||
128: []
|
||||
129: []
|
||||
130: []
|
||||
131: []
|
||||
132: []
|
||||
133: []
|
||||
134: []
|
||||
135: []
|
||||
136: []
|
||||
137: []
|
||||
138: []
|
||||
139: []
|
||||
140: []
|
||||
141: []
|
||||
142: []
|
||||
143: []
|
||||
144: []
|
||||
145: []
|
||||
146: []
|
||||
147: []
|
||||
148: []
|
||||
149: []
|
||||
150: []
|
||||
151: []
|
||||
152: []
|
||||
153: []
|
||||
154: []
|
||||
155: []
|
||||
156: []
|
||||
157: []
|
||||
158: []
|
||||
159: []
|
||||
160: []
|
||||
161: []
|
||||
162: []
|
||||
163: []
|
||||
164: []
|
||||
165: []
|
||||
166: []
|
||||
167: []
|
||||
168: []
|
||||
169: []
|
||||
170: []
|
||||
171: []
|
||||
172: []
|
||||
173: []
|
||||
174: []
|
||||
175: []
|
||||
176: []
|
||||
177: []
|
||||
178: []
|
||||
179: []
|
||||
180: []
|
||||
181: []
|
||||
182: []
|
||||
183: []
|
||||
184: []
|
||||
185: []
|
||||
186: []
|
||||
187: []
|
||||
188: []
|
||||
189: []
|
||||
190: []
|
||||
191: []
|
||||
192: []
|
||||
193: []
|
||||
194: []
|
||||
195: []
|
||||
196: []
|
||||
197: []
|
||||
198: []
|
||||
199: []
|
||||
200: []
|
||||
201: []
|
||||
202: []
|
||||
203: []
|
||||
204: []
|
||||
205: []
|
||||
206: []
|
||||
207: []
|
||||
208: []
|
||||
209: []
|
||||
210: []
|
||||
211: []
|
||||
212: []
|
||||
213: []
|
||||
214: []
|
||||
215: []
|
||||
216: []
|
||||
217: []
|
||||
218: []
|
||||
219: []
|
||||
220: []
|
||||
221: []
|
||||
222: []
|
||||
223: []
|
||||
224: []
|
||||
225: []
|
||||
226: []
|
||||
227: []
|
||||
228: []
|
||||
229: []
|
||||
230: []
|
||||
231: []
|
||||
232: []
|
||||
233: []
|
||||
234: []
|
||||
235: []
|
||||
236: []
|
||||
237: []
|
||||
238: []
|
||||
239: []
|
||||
240: []
|
||||
241: []
|
||||
242: []
|
||||
243: []
|
||||
244: []
|
||||
245: []
|
||||
246: []
|
||||
247: []
|
||||
248: []
|
||||
249: []
|
||||
250: []
|
||||
251: []
|
||||
252: []
|
||||
253: []
|
||||
254: []
|
||||
255: []
|
||||
|
@ -0,0 +1,260 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
0: []
|
||||
1: []
|
||||
2: []
|
||||
3: []
|
||||
4: []
|
||||
5: []
|
||||
6: []
|
||||
7: []
|
||||
8: []
|
||||
9: []
|
||||
10: []
|
||||
11: []
|
||||
12: []
|
||||
13: []
|
||||
14: []
|
||||
15: []
|
||||
16: []
|
||||
17: []
|
||||
18: []
|
||||
19: []
|
||||
20: []
|
||||
21: []
|
||||
22: []
|
||||
23: []
|
||||
24: []
|
||||
25: []
|
||||
26: []
|
||||
27: []
|
||||
28: []
|
||||
29: []
|
||||
30: []
|
||||
31: []
|
||||
32: []
|
||||
33: []
|
||||
34: []
|
||||
35: []
|
||||
36: []
|
||||
37: []
|
||||
38: []
|
||||
39: []
|
||||
40: []
|
||||
41: []
|
||||
42: []
|
||||
43: []
|
||||
44: []
|
||||
45: []
|
||||
46: []
|
||||
47: []
|
||||
48: []
|
||||
49: []
|
||||
50: []
|
||||
51: []
|
||||
52: []
|
||||
53: []
|
||||
54: []
|
||||
55: []
|
||||
56: []
|
||||
57: []
|
||||
58: []
|
||||
59: []
|
||||
60: []
|
||||
61: []
|
||||
62: []
|
||||
63: []
|
||||
64: []
|
||||
65: []
|
||||
66: []
|
||||
67: []
|
||||
68: []
|
||||
69: []
|
||||
70: []
|
||||
71: []
|
||||
72: []
|
||||
73: []
|
||||
74: []
|
||||
75: []
|
||||
76: []
|
||||
77: []
|
||||
78: []
|
||||
79: []
|
||||
80: []
|
||||
81: []
|
||||
82: []
|
||||
83: []
|
||||
84: []
|
||||
85: []
|
||||
86: []
|
||||
87: []
|
||||
88: []
|
||||
89: []
|
||||
90: []
|
||||
91: []
|
||||
92: []
|
||||
93: []
|
||||
94: []
|
||||
95: []
|
||||
96: []
|
||||
97: []
|
||||
98: []
|
||||
99: []
|
||||
100: []
|
||||
101: []
|
||||
102: []
|
||||
103: []
|
||||
104: []
|
||||
105: []
|
||||
106: []
|
||||
107: []
|
||||
108: []
|
||||
109: []
|
||||
110: []
|
||||
111: []
|
||||
112: []
|
||||
113: []
|
||||
114: []
|
||||
115: []
|
||||
116: []
|
||||
117: []
|
||||
118: []
|
||||
119: []
|
||||
120: []
|
||||
121: []
|
||||
122: []
|
||||
123: []
|
||||
124: []
|
||||
125: []
|
||||
126: []
|
||||
127: []
|
||||
128: []
|
||||
129: []
|
||||
130: []
|
||||
131: []
|
||||
132: []
|
||||
133: []
|
||||
134: []
|
||||
135: []
|
||||
136: []
|
||||
137: []
|
||||
138: []
|
||||
139: []
|
||||
140: []
|
||||
141: []
|
||||
142: []
|
||||
143: []
|
||||
144: []
|
||||
145: []
|
||||
146: []
|
||||
147: []
|
||||
148: []
|
||||
149: []
|
||||
150: []
|
||||
151: []
|
||||
152: []
|
||||
153: []
|
||||
154: []
|
||||
155: []
|
||||
156: []
|
||||
157: []
|
||||
158: []
|
||||
159: []
|
||||
160: []
|
||||
161: []
|
||||
162: []
|
||||
163: []
|
||||
164: []
|
||||
165: []
|
||||
166: []
|
||||
167: []
|
||||
168: []
|
||||
169: []
|
||||
170: []
|
||||
171: []
|
||||
172: []
|
||||
173: []
|
||||
174: []
|
||||
175: []
|
||||
176: []
|
||||
177: []
|
||||
178: []
|
||||
179: []
|
||||
180: []
|
||||
181: []
|
||||
182: []
|
||||
183: []
|
||||
184: []
|
||||
185: []
|
||||
186: []
|
||||
187: []
|
||||
188: []
|
||||
189: []
|
||||
190: []
|
||||
191: []
|
||||
192: []
|
||||
193: []
|
||||
194: []
|
||||
195: []
|
||||
196: []
|
||||
197: []
|
||||
198: []
|
||||
199: []
|
||||
200: []
|
||||
201: []
|
||||
202: []
|
||||
203: []
|
||||
204: []
|
||||
205: []
|
||||
206: []
|
||||
207: []
|
||||
208: []
|
||||
209: []
|
||||
210: []
|
||||
211: []
|
||||
212: []
|
||||
213: []
|
||||
214: []
|
||||
215: []
|
||||
216: []
|
||||
217: []
|
||||
218: []
|
||||
219: []
|
||||
220: []
|
||||
221: []
|
||||
222: []
|
||||
223: []
|
||||
224: []
|
||||
225: []
|
||||
226: []
|
||||
227: []
|
||||
228: []
|
||||
229: []
|
||||
230: []
|
||||
231: []
|
||||
232: []
|
||||
233: []
|
||||
234: []
|
||||
235: []
|
||||
236: []
|
||||
237: []
|
||||
238: []
|
||||
239: []
|
||||
240: []
|
||||
241: []
|
||||
242: []
|
||||
243: []
|
||||
244: []
|
||||
245: []
|
||||
246: []
|
||||
247: []
|
||||
248: []
|
||||
249: []
|
||||
250: []
|
||||
251: []
|
||||
252: []
|
||||
253: []
|
||||
254: []
|
||||
255: []
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
9c25261cec7275cb5cfd85835904d023
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
2f97f18c15e915853e4df879be6e1f63
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
e849066b0e43d5c456f086c552372afc
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
8cc5e82995b0443b660f419bb9ea2e85
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
e849066b0e43d5c456f086c552372afc
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
8cc5e82995b0443b660f419bb9ea2e85
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
73b48005dc57b04f0939bbf21a68dab6
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
3c23d35627667dcee98468bfdecf09d3
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
73b48005dc57b04f0939bbf21a68dab6
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
3c23d35627667dcee98468bfdecf09d3
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
c3f8b0b858a4820a508b25b42328cedd
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
38a42f5dc25e99d7a5312a63ce94ed30
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
c3f8b0b858a4820a508b25b42328cedd
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
38a42f5dc25e99d7a5312a63ce94ed30
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
2049930204498b323885c91de88e44ca
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
7f0ca8c0fc6494f3dba46e8eb9699045
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
2049930204498b323885c91de88e44ca
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
7f0ca8c0fc6494f3dba46e8eb9699045
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
ad8fc873747aaf1d3590e7ccab735985
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
7c6cc88697da835d33877b2df41fa1cb
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
ad8fc873747aaf1d3590e7ccab735985
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
7c6cc88697da835d33877b2df41fa1cb
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
9a8c7343b4735d37704748cabcd51ff2
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
898a7dc25a1441bc3e7e2a8a62d99090
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
9a8c7343b4735d37704748cabcd51ff2
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
898a7dc25a1441bc3e7e2a8a62d99090
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, ]
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 0: [3, 5, 6, 9, 10, 11, 14, 18, 19, 24, 26, 28, 29, 32, 33, 35, 36, 37, 38, 39, 41, 46, 47, 49, 52, 53, 55, 59, 61, 64, 68, 71, 74, 75, 76, 81, 83, 85, 86, 88, 90, 91, 92, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 111, 114, 115, 118, 119, 123, 124, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 159, 160, 161, 162, 163, 164, 167, 168, 169, 171, 173, 174, 175, 176, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 191, 192, 193, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207, 208, 209, 210, 211, 215, 216, 219, 220, 223, 224, 226, 230, 231, 233, 235, 236, 237, 238, 239, 241, 243, 244, 247, 250, 256, 258, 260, 262, 263, 264, 267, 269, 273, 277, 278, 279, 281, 282, 286, 289, 292, 293, 295, 297, 305, 306, 307, 308, 309, 310, 316, 319, 320, 323, 326, 335, 336, 338, 343, ]
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, ]
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 0: [3, 5, 6, 9, 10, 11, 14, 18, 19, 24, 26, 28, 29, 32, 33, 35, 36, 37, 38, 39, 41, 46, 47, 49, 52, 53, 55, 59, 61, 64, 68, 71, 74, 75, 76, 81, 83, 85, 86, 88, 90, 91, 92, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 111, 114, 115, 118, 119, 123, 124, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 159, 160, 161, 162, 163, 164, 167, 168, 169, 171, 173, 174, 175, 176, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 191, 192, 193, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207, 208, 209, 210, 211, 215, 216, 219, 220, 223, 224, 226, 230, 231, 233, 235, 236, 237, 238, 239, 241, 243, 244, 247, 250, 256, 258, 260, 262, 263, 264, 267, 269, 273, 277, 278, 279, 281, 282, 286, 289, 292, 293, 295, 297, 305, 306, 307, 308, 309, 310, 316, 319, 320, 323, 326, 335, 336, 338, 343, ]
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 1: []
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 1: []
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, ]
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_range_search.rs
|
||||
---
|
||||
all field_id 1: [3, 5, 6, 9, 10, 11, 14, 18, 19, 24, 26, 28, 29, 32, 33, 35, 36, 37, 38, 39, 41, 46, 47, 49, 52, 53, 55, 59, 61, 64, 68, 71, 74, 75, 76, 81, 83, 85, 86, 88, 90, 91, 92, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 111, 114, 115, 118, 119, 123, 124, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 159, 160, 161, 162, 163, 164, 167, 168, 169, 171, 173, 174, 175, 176, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 191, 192, 193, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207, 208, 209, 210, 211, 215, 216, 219, 220, 223, 224, 226, 230, 231, 233, 235, 236, 237, 238, 239, 241, 243, 244, 247, 250, 256, 258, 260, 262, 263, 264, 267, 269, 273, 277, 278, 279, 281, 282, 286, 289, 292, 293, 295, 297, 305, 306, 307, 308, 309, 310, 316, 319, 320, 323, 326, 335, 336, 338, 343, ]
|
||||
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[200, ]
|
||||
[201, ]
|
||||
[202, ]
|
||||
[203, ]
|
||||
[204, ]
|
||||
[205, ]
|
||||
[206, ]
|
||||
[207, ]
|
||||
[208, ]
|
||||
[209, ]
|
||||
[210, ]
|
||||
[211, ]
|
||||
[212, ]
|
||||
[213, ]
|
||||
[214, ]
|
||||
[215, ]
|
||||
[216, ]
|
||||
[217, ]
|
||||
[218, ]
|
||||
[219, ]
|
||||
[220, ]
|
||||
[221, ]
|
||||
[222, ]
|
||||
[223, ]
|
||||
[224, ]
|
||||
[225, ]
|
||||
[226, ]
|
||||
[227, ]
|
||||
[228, ]
|
||||
[229, ]
|
||||
[230, ]
|
||||
[231, ]
|
||||
[232, ]
|
||||
[233, ]
|
||||
[234, ]
|
||||
[235, ]
|
||||
[236, ]
|
||||
[237, ]
|
||||
[238, ]
|
||||
[239, ]
|
||||
[240, ]
|
||||
[241, ]
|
||||
[242, ]
|
||||
[243, ]
|
||||
[244, ]
|
||||
[245, ]
|
||||
[246, ]
|
||||
[247, ]
|
||||
[248, ]
|
||||
[249, ]
|
||||
[250, ]
|
||||
[251, ]
|
||||
[252, ]
|
||||
[253, ]
|
||||
[254, ]
|
||||
[255, ]
|
||||
|
@ -0,0 +1,54 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[201, ]
|
||||
[202, ]
|
||||
[203, ]
|
||||
[207, ]
|
||||
[211, ]
|
||||
[215, ]
|
||||
[219, ]
|
||||
[223, ]
|
||||
[224, ]
|
||||
[230, ]
|
||||
[231, ]
|
||||
[233, ]
|
||||
[235, ]
|
||||
[236, ]
|
||||
[237, ]
|
||||
[239, ]
|
||||
[241, ]
|
||||
[243, ]
|
||||
[244, ]
|
||||
[247, ]
|
||||
[250, ]
|
||||
[256, ]
|
||||
[258, ]
|
||||
[260, ]
|
||||
[262, ]
|
||||
[263, ]
|
||||
[264, ]
|
||||
[267, ]
|
||||
[269, ]
|
||||
[273, ]
|
||||
[277, ]
|
||||
[278, ]
|
||||
[279, ]
|
||||
[281, ]
|
||||
[282, ]
|
||||
[286, ]
|
||||
[289, ]
|
||||
[292, ]
|
||||
[293, ]
|
||||
[295, ]
|
||||
[297, ]
|
||||
[205, ]
|
||||
[206, ]
|
||||
[208, ]
|
||||
[209, ]
|
||||
[210, ]
|
||||
[216, ]
|
||||
[220, ]
|
||||
[226, ]
|
||||
[238, ]
|
||||
|
@ -0,0 +1,33 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, ]
|
||||
[200, ]
|
||||
[202, ]
|
||||
[204, ]
|
||||
[206, ]
|
||||
[208, ]
|
||||
[210, ]
|
||||
[212, ]
|
||||
[214, ]
|
||||
[216, ]
|
||||
[218, ]
|
||||
[220, ]
|
||||
[222, ]
|
||||
[224, ]
|
||||
[226, ]
|
||||
[228, ]
|
||||
[230, ]
|
||||
[232, ]
|
||||
[234, ]
|
||||
[236, ]
|
||||
[238, ]
|
||||
[240, ]
|
||||
[242, ]
|
||||
[244, ]
|
||||
[246, ]
|
||||
[248, ]
|
||||
[250, ]
|
||||
[252, ]
|
||||
[254, ]
|
||||
|
@ -0,0 +1,33 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, ]
|
||||
[200, ]
|
||||
[202, ]
|
||||
[204, ]
|
||||
[206, ]
|
||||
[208, ]
|
||||
[210, ]
|
||||
[212, ]
|
||||
[214, ]
|
||||
[216, ]
|
||||
[218, ]
|
||||
[220, ]
|
||||
[222, ]
|
||||
[224, ]
|
||||
[226, ]
|
||||
[228, ]
|
||||
[230, ]
|
||||
[232, ]
|
||||
[234, ]
|
||||
[236, ]
|
||||
[238, ]
|
||||
[240, ]
|
||||
[242, ]
|
||||
[244, ]
|
||||
[246, ]
|
||||
[248, ]
|
||||
[250, ]
|
||||
[252, ]
|
||||
[254, ]
|
||||
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[201, 203, 205, 207, 209, 211, 215, 219, 223, 231, 233, 235, 237, 239, 241, 243, 247, 263, 267, 269, 273, 277, 279, 281, 289, 293, 295, 297, ]
|
||||
[202, ]
|
||||
[224, ]
|
||||
[230, ]
|
||||
[236, ]
|
||||
[244, ]
|
||||
[250, ]
|
||||
[256, ]
|
||||
[258, ]
|
||||
[260, ]
|
||||
[262, ]
|
||||
[264, ]
|
||||
[278, ]
|
||||
[282, ]
|
||||
[286, ]
|
||||
[292, ]
|
||||
[206, ]
|
||||
[208, ]
|
||||
[210, ]
|
||||
[216, ]
|
||||
[220, ]
|
||||
[226, ]
|
||||
[238, ]
|
||||
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_ascending.rs
|
||||
---
|
||||
[201, 203, 205, 207, 209, 211, 215, 219, 223, 231, 233, 235, 237, 239, 241, 243, 247, 263, 267, 269, 273, 277, 279, 281, 289, 293, 295, 297, ]
|
||||
[202, ]
|
||||
[224, ]
|
||||
[230, ]
|
||||
[236, ]
|
||||
[244, ]
|
||||
[250, ]
|
||||
[256, ]
|
||||
[258, ]
|
||||
[260, ]
|
||||
[262, ]
|
||||
[264, ]
|
||||
[278, ]
|
||||
[282, ]
|
||||
[286, ]
|
||||
[292, ]
|
||||
[206, ]
|
||||
[208, ]
|
||||
[210, ]
|
||||
[216, ]
|
||||
[220, ]
|
||||
[226, ]
|
||||
[238, ]
|
||||
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[255, ]
|
||||
[254, ]
|
||||
[253, ]
|
||||
[252, ]
|
||||
[251, ]
|
||||
[250, ]
|
||||
[249, ]
|
||||
[248, ]
|
||||
[247, ]
|
||||
[246, ]
|
||||
[245, ]
|
||||
[244, ]
|
||||
[243, ]
|
||||
[242, ]
|
||||
[241, ]
|
||||
[240, ]
|
||||
[239, ]
|
||||
[238, ]
|
||||
[237, ]
|
||||
[236, ]
|
||||
[235, ]
|
||||
[234, ]
|
||||
[233, ]
|
||||
[232, ]
|
||||
[231, ]
|
||||
[230, ]
|
||||
[229, ]
|
||||
[228, ]
|
||||
[227, ]
|
||||
[226, ]
|
||||
[225, ]
|
||||
[224, ]
|
||||
[223, ]
|
||||
[222, ]
|
||||
[221, ]
|
||||
[220, ]
|
||||
[219, ]
|
||||
[218, ]
|
||||
[217, ]
|
||||
[216, ]
|
||||
[215, ]
|
||||
[214, ]
|
||||
[213, ]
|
||||
[212, ]
|
||||
[211, ]
|
||||
[210, ]
|
||||
[209, ]
|
||||
[208, ]
|
||||
[207, ]
|
||||
[206, ]
|
||||
[205, ]
|
||||
[204, ]
|
||||
[203, ]
|
||||
[202, ]
|
||||
[201, ]
|
||||
[200, ]
|
||||
|
@ -0,0 +1,54 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[243, ]
|
||||
[238, ]
|
||||
[236, ]
|
||||
[235, ]
|
||||
[226, ]
|
||||
[223, ]
|
||||
[220, ]
|
||||
[219, ]
|
||||
[216, ]
|
||||
[210, ]
|
||||
[209, ]
|
||||
[208, ]
|
||||
[207, ]
|
||||
[206, ]
|
||||
[205, ]
|
||||
[297, ]
|
||||
[295, ]
|
||||
[293, ]
|
||||
[292, ]
|
||||
[289, ]
|
||||
[286, ]
|
||||
[282, ]
|
||||
[281, ]
|
||||
[279, ]
|
||||
[278, ]
|
||||
[277, ]
|
||||
[273, ]
|
||||
[269, ]
|
||||
[267, ]
|
||||
[264, ]
|
||||
[263, ]
|
||||
[262, ]
|
||||
[260, ]
|
||||
[258, ]
|
||||
[256, ]
|
||||
[250, ]
|
||||
[247, ]
|
||||
[244, ]
|
||||
[241, ]
|
||||
[239, ]
|
||||
[237, ]
|
||||
[233, ]
|
||||
[231, ]
|
||||
[230, ]
|
||||
[224, ]
|
||||
[215, ]
|
||||
[211, ]
|
||||
[203, ]
|
||||
[202, ]
|
||||
[201, ]
|
||||
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[255, ]
|
||||
[254, ]
|
||||
[253, ]
|
||||
[252, ]
|
||||
[251, ]
|
||||
[250, ]
|
||||
[249, ]
|
||||
[248, ]
|
||||
[247, ]
|
||||
[246, ]
|
||||
[245, ]
|
||||
[244, ]
|
||||
[243, ]
|
||||
[242, ]
|
||||
[241, ]
|
||||
[240, ]
|
||||
[239, ]
|
||||
[238, ]
|
||||
[237, ]
|
||||
[236, ]
|
||||
[235, ]
|
||||
[234, ]
|
||||
[233, ]
|
||||
[232, ]
|
||||
[231, ]
|
||||
[230, ]
|
||||
[229, ]
|
||||
[228, ]
|
||||
[227, ]
|
||||
[226, ]
|
||||
[225, ]
|
||||
[224, ]
|
||||
[223, ]
|
||||
[222, ]
|
||||
[221, ]
|
||||
[220, ]
|
||||
[219, ]
|
||||
[218, ]
|
||||
[217, ]
|
||||
[216, ]
|
||||
[215, ]
|
||||
[214, ]
|
||||
[213, ]
|
||||
[212, ]
|
||||
[211, ]
|
||||
[210, ]
|
||||
[209, ]
|
||||
[208, ]
|
||||
[207, ]
|
||||
[206, ]
|
||||
[205, ]
|
||||
[204, ]
|
||||
[203, ]
|
||||
[202, ]
|
||||
[201, ]
|
||||
[200, ]
|
||||
|
@ -0,0 +1,33 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[254, ]
|
||||
[252, ]
|
||||
[250, ]
|
||||
[248, ]
|
||||
[246, ]
|
||||
[244, ]
|
||||
[242, ]
|
||||
[240, ]
|
||||
[238, ]
|
||||
[236, ]
|
||||
[234, ]
|
||||
[232, ]
|
||||
[230, ]
|
||||
[228, ]
|
||||
[226, ]
|
||||
[224, ]
|
||||
[222, ]
|
||||
[220, ]
|
||||
[218, ]
|
||||
[216, ]
|
||||
[214, ]
|
||||
[212, ]
|
||||
[210, ]
|
||||
[208, ]
|
||||
[206, ]
|
||||
[204, ]
|
||||
[202, ]
|
||||
[200, ]
|
||||
[201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, ]
|
||||
|
@ -0,0 +1,33 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[254, ]
|
||||
[252, ]
|
||||
[250, ]
|
||||
[248, ]
|
||||
[246, ]
|
||||
[244, ]
|
||||
[242, ]
|
||||
[240, ]
|
||||
[238, ]
|
||||
[236, ]
|
||||
[234, ]
|
||||
[232, ]
|
||||
[230, ]
|
||||
[228, ]
|
||||
[226, ]
|
||||
[224, ]
|
||||
[222, ]
|
||||
[220, ]
|
||||
[218, ]
|
||||
[216, ]
|
||||
[214, ]
|
||||
[212, ]
|
||||
[210, ]
|
||||
[208, ]
|
||||
[206, ]
|
||||
[204, ]
|
||||
[202, ]
|
||||
[200, ]
|
||||
[201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, ]
|
||||
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[238, ]
|
||||
[236, ]
|
||||
[226, ]
|
||||
[220, ]
|
||||
[216, ]
|
||||
[210, ]
|
||||
[208, ]
|
||||
[206, ]
|
||||
[292, ]
|
||||
[286, ]
|
||||
[282, ]
|
||||
[278, ]
|
||||
[264, ]
|
||||
[262, ]
|
||||
[260, ]
|
||||
[258, ]
|
||||
[256, ]
|
||||
[250, ]
|
||||
[244, ]
|
||||
[230, ]
|
||||
[224, ]
|
||||
[202, ]
|
||||
[201, 203, 205, 207, 209, 211, 215, 219, 223, 231, 233, 235, 237, 239, 241, 243, 247, 263, 267, 269, 273, 277, 279, 281, 289, 293, 295, 297, ]
|
||||
|
@ -0,0 +1,27 @@
|
||||
---
|
||||
source: milli/src/search/facet/facet_sort_descending.rs
|
||||
---
|
||||
[238, ]
|
||||
[236, ]
|
||||
[226, ]
|
||||
[220, ]
|
||||
[216, ]
|
||||
[210, ]
|
||||
[208, ]
|
||||
[206, ]
|
||||
[292, ]
|
||||
[286, ]
|
||||
[282, ]
|
||||
[278, ]
|
||||
[264, ]
|
||||
[262, ]
|
||||
[260, ]
|
||||
[258, ]
|
||||
[256, ]
|
||||
[250, ]
|
||||
[244, ]
|
||||
[230, ]
|
||||
[224, ]
|
||||
[202, ]
|
||||
[201, 203, 205, 207, 209, 211, 215, 219, 223, 231, 233, 235, 237, 239, 241, 243, 247, 263, 267, 269, 273, 277, 279, 281, 289, 293, 295, 297, ]
|
||||
|
Reference in New Issue
Block a user