mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-30 09:04:59 +08:00
fix typo and remove pest
This commit is contained in:
parent
50ad750ec1
commit
ac1df9d9d7
@ -2,15 +2,10 @@ use std::collections::HashSet;
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Bound::{self, Excluded, Included};
|
use std::ops::Bound::{self, Excluded, Included};
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use heed::types::DecodeIgnore;
|
use heed::types::DecodeIgnore;
|
||||||
use itertools::Itertools;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use pest::error::{Error as PestError, ErrorVariant};
|
|
||||||
use pest::iterators::{Pair, Pairs};
|
|
||||||
use pest::Parser;
|
|
||||||
use roaring::RoaringBitmap;
|
use roaring::RoaringBitmap;
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
@ -28,7 +23,8 @@ use nom::{
|
|||||||
|
|
||||||
use self::FilterCondition::*;
|
use self::FilterCondition::*;
|
||||||
use self::Operator::*;
|
use self::Operator::*;
|
||||||
use super::parser::{FilterParser, Rule, PREC_CLIMBER};
|
use super::parser::FilterParser;
|
||||||
|
use super::parser::{Rule, PREC_CLIMBER};
|
||||||
use super::FacetNumberRange;
|
use super::FacetNumberRange;
|
||||||
use crate::error::{Error, UserError};
|
use crate::error::{Error, UserError};
|
||||||
use crate::heed_codec::facet::{
|
use crate::heed_codec::facet::{
|
||||||
@ -145,9 +141,7 @@ impl<'a> ParseContext<'a> {
|
|||||||
let k = match op {
|
let k = match op {
|
||||||
"=" => Operator(fid, Equal(r.ok(), value.to_string().to_lowercase())),
|
"=" => Operator(fid, Equal(r.ok(), value.to_string().to_lowercase())),
|
||||||
"!=" => Operator(fid, NotEqual(r.ok(), value.to_string().to_lowercase())),
|
"!=" => Operator(fid, NotEqual(r.ok(), value.to_string().to_lowercase())),
|
||||||
">" | "<" | "<=" | ">=" => {
|
">" | "<" | "<=" | ">=" => return self.parse_numeric_unary_condition(op, fid, value),
|
||||||
return self.parse_numeric_unary_condition(input, fid, value)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Ok((input, k))
|
Ok((input, k))
|
||||||
@ -326,92 +320,6 @@ impl FilterCondition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FilterCondition {
|
impl FilterCondition {
|
||||||
pub fn from_array_pest<I, J, A, B>(
|
|
||||||
rtxn: &heed::RoTxn,
|
|
||||||
index: &Index,
|
|
||||||
array: I,
|
|
||||||
) -> Result<Option<FilterCondition>>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = Either<J, B>>,
|
|
||||||
J: IntoIterator<Item = A>,
|
|
||||||
A: AsRef<str>,
|
|
||||||
B: AsRef<str>,
|
|
||||||
{
|
|
||||||
let mut ands = None;
|
|
||||||
|
|
||||||
for either in array {
|
|
||||||
match either {
|
|
||||||
Either::Left(array) => {
|
|
||||||
let mut ors = None;
|
|
||||||
for rule in array {
|
|
||||||
let condition = FilterCondition::from_str(rtxn, index, rule.as_ref())?;
|
|
||||||
ors = match ors.take() {
|
|
||||||
Some(ors) => Some(Or(Box::new(ors), Box::new(condition))),
|
|
||||||
None => Some(condition),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rule) = ors {
|
|
||||||
ands = match ands.take() {
|
|
||||||
Some(ands) => Some(And(Box::new(ands), Box::new(rule))),
|
|
||||||
None => Some(rule),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Either::Right(rule) => {
|
|
||||||
let condition = FilterCondition::from_str(rtxn, index, rule.as_ref())?;
|
|
||||||
ands = match ands.take() {
|
|
||||||
Some(ands) => Some(And(Box::new(ands), Box::new(condition))),
|
|
||||||
None => Some(condition),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ands)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_str_pest(
|
|
||||||
rtxn: &heed::RoTxn,
|
|
||||||
index: &Index,
|
|
||||||
expression: &str,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let fields_ids_map = index.fields_ids_map(rtxn)?;
|
|
||||||
let filterable_fields = index.filterable_fields(rtxn)?;
|
|
||||||
let lexed =
|
|
||||||
FilterParser::parse(Rule::prgm, expression).map_err(UserError::InvalidFilter)?;
|
|
||||||
FilterCondition::from_pairs(&fields_ids_map, &filterable_fields, lexed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_pairs(
|
|
||||||
fim: &FieldsIdsMap,
|
|
||||||
ff: &HashSet<String>,
|
|
||||||
expression: Pairs<Rule>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
PREC_CLIMBER.climb(
|
|
||||||
expression,
|
|
||||||
|pair: Pair<Rule>| match pair.as_rule() {
|
|
||||||
Rule::greater => Ok(Self::greater_than(fim, ff, pair)?),
|
|
||||||
Rule::geq => Ok(Self::greater_than_or_equal(fim, ff, pair)?),
|
|
||||||
Rule::eq => Ok(Self::equal(fim, ff, pair)?),
|
|
||||||
Rule::neq => Ok(Self::equal(fim, ff, pair)?.negate()),
|
|
||||||
Rule::leq => Ok(Self::lower_than_or_equal(fim, ff, pair)?),
|
|
||||||
Rule::less => Ok(Self::lower_than(fim, ff, pair)?),
|
|
||||||
Rule::between => Ok(Self::between(fim, ff, pair)?),
|
|
||||||
Rule::geo_radius => Ok(Self::geo_radius(fim, ff, pair)?),
|
|
||||||
Rule::not => Ok(Self::from_pairs(fim, ff, pair.into_inner())?.negate()),
|
|
||||||
Rule::prgm => Self::from_pairs(fim, ff, pair.into_inner()),
|
|
||||||
Rule::term => Self::from_pairs(fim, ff, pair.into_inner()),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
|lhs: Result<Self>, op: Pair<Rule>, rhs: Result<Self>| match op.as_rule() {
|
|
||||||
Rule::or => Ok(Or(Box::new(lhs?), Box::new(rhs?))),
|
|
||||||
Rule::and => Ok(And(Box::new(lhs?), Box::new(rhs?))),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn negate(self) -> FilterCondition {
|
fn negate(self) -> FilterCondition {
|
||||||
match self {
|
match self {
|
||||||
Operator(fid, op) => match op.negate() {
|
Operator(fid, op) => match op.negate() {
|
||||||
@ -484,128 +392,6 @@ impl FilterCondition {
|
|||||||
}
|
}
|
||||||
Ok(Operator(fid, GeoLowerThan([lat.0, lng.0], distance)))
|
Ok(Operator(fid, GeoLowerThan([lat.0, lng.0], distance)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn between(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (lresult, _) = pest_parse(items.next().unwrap());
|
|
||||||
let (rresult, _) = pest_parse(items.next().unwrap());
|
|
||||||
|
|
||||||
let lvalue = lresult.map_err(UserError::InvalidFilter)?;
|
|
||||||
let rvalue = rresult.map_err(UserError::InvalidFilter)?;
|
|
||||||
|
|
||||||
Ok(Operator(fid, Between(lvalue, rvalue)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn equal(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = items.next().unwrap();
|
|
||||||
let (result, svalue) = pest_parse(value);
|
|
||||||
|
|
||||||
let svalue = svalue.to_lowercase();
|
|
||||||
Ok(Operator(fid, Equal(result.ok(), svalue)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn greater_than(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = items.next().unwrap();
|
|
||||||
let (result, _svalue) = pest_parse(value);
|
|
||||||
let value = result.map_err(UserError::InvalidFilter)?;
|
|
||||||
|
|
||||||
Ok(Operator(fid, GreaterThan(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn greater_than_or_equal(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = items.next().unwrap();
|
|
||||||
let (result, _svalue) = pest_parse(value);
|
|
||||||
let value = result.map_err(UserError::InvalidFilter)?;
|
|
||||||
|
|
||||||
Ok(Operator(fid, GreaterThanOrEqual(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lower_than(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = items.next().unwrap();
|
|
||||||
let (result, _svalue) = pest_parse(value);
|
|
||||||
let value = result.map_err(UserError::InvalidFilter)?;
|
|
||||||
|
|
||||||
Ok(Operator(fid, LowerThan(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lower_than_or_equal(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
item: Pair<Rule>,
|
|
||||||
) -> Result<FilterCondition> {
|
|
||||||
let mut items = item.into_inner();
|
|
||||||
let fid = match field_id(fields_ids_map, filterable_fields, &mut items)
|
|
||||||
.map_err(UserError::InvalidFilterAttribute)?
|
|
||||||
{
|
|
||||||
Some(fid) => fid,
|
|
||||||
None => return Ok(Empty),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = items.next().unwrap();
|
|
||||||
let (result, _svalue) = pest_parse(value);
|
|
||||||
let value = result.map_err(UserError::InvalidFilter)?;
|
|
||||||
|
|
||||||
Ok(Operator(fid, LowerThanOrEqual(value)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterCondition {
|
impl FilterCondition {
|
||||||
@ -855,72 +641,6 @@ impl FilterCondition {
|
|||||||
///
|
///
|
||||||
/// The pest pair is simply a string associated with a span, a location to highlight in
|
/// The pest pair is simply a string associated with a span, a location to highlight in
|
||||||
/// the error message.
|
/// the error message.
|
||||||
fn field_id(
|
|
||||||
fields_ids_map: &FieldsIdsMap,
|
|
||||||
filterable_fields: &HashSet<String>,
|
|
||||||
items: &mut Pairs<Rule>,
|
|
||||||
) -> StdResult<Option<FieldId>, PestError<Rule>> {
|
|
||||||
// lexing ensures that we at least have a key
|
|
||||||
let key = items.next().unwrap();
|
|
||||||
if key.as_rule() == Rule::reserved {
|
|
||||||
let message = match key.as_str() {
|
|
||||||
key if key.starts_with("_geoPoint") => {
|
|
||||||
format!(
|
|
||||||
"`_geoPoint` is a reserved keyword and thus can't be used as a filter expression. \
|
|
||||||
Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
key @ "_geo" => {
|
|
||||||
format!(
|
|
||||||
"`{}` is a reserved keyword and thus can't be used as a filter expression. \
|
|
||||||
Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates.",
|
|
||||||
key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
key => format!(
|
|
||||||
"`{}` is a reserved keyword and thus can't be used as a filter expression.",
|
|
||||||
key
|
|
||||||
),
|
|
||||||
};
|
|
||||||
return Err(PestError::new_from_span(ErrorVariant::CustomError { message }, key.as_span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filterable_fields.contains(key.as_str()) {
|
|
||||||
return Err(PestError::new_from_span(
|
|
||||||
ErrorVariant::CustomError {
|
|
||||||
message: format!(
|
|
||||||
"attribute `{}` is not filterable, available filterable attributes are: {}.",
|
|
||||||
key.as_str(),
|
|
||||||
filterable_fields.iter().join(", "),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
key.as_span(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(fields_ids_map.id(key.as_str()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to parse the pest pair into the type `T` specified, always returns
|
|
||||||
/// the original string that we tried to parse.
|
|
||||||
///
|
|
||||||
/// Returns the parsing error associated with the span if the conversion fails.
|
|
||||||
fn pest_parse<T>(pair: Pair<Rule>) -> (StdResult<T, pest::error::Error<Rule>>, String)
|
|
||||||
where
|
|
||||||
T: FromStr,
|
|
||||||
T::Err: ToString,
|
|
||||||
{
|
|
||||||
let result = match pair.as_str().parse::<T>() {
|
|
||||||
Ok(value) => Ok(value),
|
|
||||||
Err(e) => Err(PestError::<Rule>::new_from_span(
|
|
||||||
ErrorVariant::CustomError { message: e.to_string() },
|
|
||||||
pair.as_span(),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
(result, pair.as_str().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use big_s::S;
|
use big_s::S;
|
||||||
@ -991,6 +711,27 @@ mod tests {
|
|||||||
assert_eq!(condition, expected);
|
assert_eq!(condition, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compare() {
|
||||||
|
let path = tempfile::tempdir().unwrap();
|
||||||
|
let mut options = EnvOpenOptions::new();
|
||||||
|
options.map_size(10 * 1024 * 1024); // 10 MB
|
||||||
|
let index = Index::new(options, &path).unwrap();
|
||||||
|
|
||||||
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
let mut builder = Settings::new(&mut wtxn, &index, 0);
|
||||||
|
builder.set_searchable_fields(vec![S("channel"), S("timestamp")]); // to keep the fields order
|
||||||
|
builder.set_filterable_fields(hashset! { S("channel"), S("timestamp") });
|
||||||
|
builder.execute(|_, _| ()).unwrap();
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
let rtxn = index.read_txn().unwrap();
|
||||||
|
let condition = FilterCondition::from_str(&rtxn, &index, "channel < 20").unwrap();
|
||||||
|
let expected = Operator(0, LowerThan(20.0));
|
||||||
|
|
||||||
|
assert_eq!(condition, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parentheses() {
|
fn parentheses() {
|
||||||
let path = tempfile::tempdir().unwrap();
|
let path = tempfile::tempdir().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user