mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-27 07:14:26 +01:00
Support a basic version of the string facet query system
This commit is contained in:
parent
498f0d8539
commit
c52d09d5b1
@ -5,19 +5,21 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use anyhow::{bail, ensure, Context};
|
use anyhow::{bail, ensure, Context};
|
||||||
use heed::types::{ByteSlice, DecodeIgnore};
|
use heed::types::{ByteSlice, DecodeIgnore};
|
||||||
|
use itertools::Itertools;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use num_traits::Bounded;
|
use num_traits::Bounded;
|
||||||
use roaring::RoaringBitmap;
|
use roaring::RoaringBitmap;
|
||||||
|
|
||||||
use crate::facet::FacetType;
|
use crate::facet::FacetType;
|
||||||
|
use crate::heed_codec::facet::FacetValueStringCodec;
|
||||||
use crate::heed_codec::facet::{FacetLevelValueI64Codec, FacetLevelValueF64Codec};
|
use crate::heed_codec::facet::{FacetLevelValueI64Codec, FacetLevelValueF64Codec};
|
||||||
use crate::{Index, CboRoaringBitmapCodec};
|
use crate::{Index, CboRoaringBitmapCodec};
|
||||||
|
|
||||||
use self::FacetCondition::*;
|
use self::FacetCondition::*;
|
||||||
use self::FacetOperator::*;
|
use self::FacetNumberOperator::*;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum FacetOperator<T> {
|
pub enum FacetNumberOperator<T> {
|
||||||
GreaterThan(T),
|
GreaterThan(T),
|
||||||
GreaterThanOrEqual(T),
|
GreaterThanOrEqual(T),
|
||||||
LowerThan(T),
|
LowerThan(T),
|
||||||
@ -26,11 +28,17 @@ pub enum FacetOperator<T> {
|
|||||||
Between(T, T),
|
Between(T, T),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum FacetStringOperator {
|
||||||
|
Equal(String),
|
||||||
|
}
|
||||||
|
|
||||||
// TODO also support ANDs, ORs, NOTs.
|
// TODO also support ANDs, ORs, NOTs.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum FacetCondition {
|
pub enum FacetCondition {
|
||||||
OperatorI64(u8, FacetOperator<i64>),
|
OperatorI64(u8, FacetNumberOperator<i64>),
|
||||||
OperatorF64(u8, FacetOperator<f64>),
|
OperatorF64(u8, FacetNumberOperator<f64>),
|
||||||
|
OperatorString(u8, FacetStringOperator),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FacetCondition {
|
impl FacetCondition {
|
||||||
@ -55,15 +63,34 @@ impl FacetCondition {
|
|||||||
let field_type = faceted_fields.get(&field_id).with_context(|| format!("field {} is not faceted", field_name))?;
|
let field_type = faceted_fields.get(&field_id).with_context(|| format!("field {} is not faceted", field_name))?;
|
||||||
|
|
||||||
match field_type {
|
match field_type {
|
||||||
FacetType::Integer => Self::parse_condition(iter).map(|op| Some(OperatorI64(field_id, op))),
|
FacetType::Integer => Self::parse_number_condition(iter).map(|op| Some(OperatorI64(field_id, op))),
|
||||||
FacetType::Float => Self::parse_condition(iter).map(|op| Some(OperatorF64(field_id, op))),
|
FacetType::Float => Self::parse_number_condition(iter).map(|op| Some(OperatorF64(field_id, op))),
|
||||||
FacetType::String => bail!("invalid facet type"),
|
FacetType::String => Self::parse_string_condition(iter).map(|op| Some(OperatorString(field_id, op))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_condition<'a, T: FromStr>(
|
fn parse_string_condition<'a>(
|
||||||
mut iter: impl Iterator<Item=&'a str>,
|
mut iter: impl Iterator<Item=&'a str>,
|
||||||
) -> anyhow::Result<FacetOperator<T>>
|
) -> anyhow::Result<FacetStringOperator>
|
||||||
|
{
|
||||||
|
match iter.next() {
|
||||||
|
Some("=") | Some(":") => {
|
||||||
|
match iter.next() {
|
||||||
|
Some(q @ "\"") | Some(q @ "\'") => {
|
||||||
|
let string: String = iter.take_while(|&c| c != q).intersperse(" ").collect();
|
||||||
|
Ok(FacetStringOperator::Equal(string.to_lowercase()))
|
||||||
|
},
|
||||||
|
Some(param) => Ok(FacetStringOperator::Equal(param.to_lowercase())),
|
||||||
|
None => bail!("missing parameter"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => bail!("invalid facet string operator"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_number_condition<'a, T: FromStr>(
|
||||||
|
mut iter: impl Iterator<Item=&'a str>,
|
||||||
|
) -> anyhow::Result<FacetNumberOperator<T>>
|
||||||
where T::Err: Send + Sync + StdError + 'static,
|
where T::Err: Send + Sync + StdError + 'static,
|
||||||
{
|
{
|
||||||
match iter.next() {
|
match iter.next() {
|
||||||
@ -201,11 +228,11 @@ impl FacetCondition {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_operator<'t, T: 't, KC>(
|
fn evaluate_number_operator<'t, T: 't, KC>(
|
||||||
rtxn: &'t heed::RoTxn,
|
rtxn: &'t heed::RoTxn,
|
||||||
db: heed::Database<ByteSlice, CboRoaringBitmapCodec>,
|
db: heed::Database<ByteSlice, CboRoaringBitmapCodec>,
|
||||||
field_id: u8,
|
field_id: u8,
|
||||||
operator: FacetOperator<T>,
|
operator: FacetNumberOperator<T>,
|
||||||
) -> anyhow::Result<RoaringBitmap>
|
) -> anyhow::Result<RoaringBitmap>
|
||||||
where
|
where
|
||||||
T: Copy + PartialEq + PartialOrd + Bounded + Debug,
|
T: Copy + PartialEq + PartialOrd + Bounded + Debug,
|
||||||
@ -241,19 +268,40 @@ impl FacetCondition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evaluate_string_operator(
|
||||||
|
rtxn: &heed::RoTxn,
|
||||||
|
db: heed::Database<FacetValueStringCodec, CboRoaringBitmapCodec>,
|
||||||
|
field_id: u8,
|
||||||
|
operator: &FacetStringOperator,
|
||||||
|
) -> anyhow::Result<RoaringBitmap>
|
||||||
|
{
|
||||||
|
match operator {
|
||||||
|
FacetStringOperator::Equal(string) => {
|
||||||
|
match db.get(rtxn, &(field_id, string))? {
|
||||||
|
Some(docids) => Ok(docids),
|
||||||
|
None => Ok(RoaringBitmap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn evaluate(
|
pub fn evaluate(
|
||||||
&self,
|
&self,
|
||||||
rtxn: &heed::RoTxn,
|
rtxn: &heed::RoTxn,
|
||||||
db: heed::Database<ByteSlice, CboRoaringBitmapCodec>,
|
db: heed::Database<ByteSlice, CboRoaringBitmapCodec>,
|
||||||
) -> anyhow::Result<RoaringBitmap>
|
) -> anyhow::Result<RoaringBitmap>
|
||||||
{
|
{
|
||||||
match *self {
|
match self {
|
||||||
FacetCondition::OperatorI64(fid, operator) => {
|
OperatorI64(fid, op) => {
|
||||||
Self::evaluate_operator::<i64, FacetLevelValueI64Codec>(rtxn, db, fid, operator)
|
Self::evaluate_number_operator::<i64, FacetLevelValueI64Codec>(rtxn, db, *fid, *op)
|
||||||
|
},
|
||||||
|
OperatorF64(fid, op) => {
|
||||||
|
Self::evaluate_number_operator::<f64, FacetLevelValueF64Codec>(rtxn, db, *fid, *op)
|
||||||
|
},
|
||||||
|
OperatorString(fid, op) => {
|
||||||
|
let db = db.remap_key_type::<FacetValueStringCodec>();
|
||||||
|
Self::evaluate_string_operator(rtxn, db, *fid, op)
|
||||||
},
|
},
|
||||||
FacetCondition::OperatorF64(fid, operator) => {
|
|
||||||
Self::evaluate_operator::<f64, FacetLevelValueF64Codec>(rtxn, db, fid, operator)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ impl<'a> Search<'a> {
|
|||||||
|
|
||||||
// We create the original candidates with the facet conditions results.
|
// We create the original candidates with the facet conditions results.
|
||||||
let facet_db = self.index.facet_field_id_value_docids;
|
let facet_db = self.index.facet_field_id_value_docids;
|
||||||
let facet_candidates = match self.facet_condition {
|
let facet_candidates = match &self.facet_condition {
|
||||||
Some(condition) => Some(condition.evaluate(self.rtxn, facet_db)?),
|
Some(condition) => Some(condition.evaluate(self.rtxn, facet_db)?),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
@ -586,7 +586,7 @@ fn parse_facet_value(ftype: FacetType, value: &Value) -> anyhow::Result<SmallVec
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Value::String(string) => {
|
Value::String(string) => {
|
||||||
let string = string.trim();
|
let string = string.trim().to_lowercase();
|
||||||
if string.is_empty() { return Ok(()) }
|
if string.is_empty() { return Ok(()) }
|
||||||
match ftype {
|
match ftype {
|
||||||
FacetType::String => {
|
FacetType::String => {
|
||||||
|
Loading…
Reference in New Issue
Block a user