mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-28 16:15:42 +08:00
Compare commits
2 Commits
5449d59fd2
...
f91c174936
Author | SHA1 | Date | |
---|---|---|---|
|
f91c174936 | ||
|
057fcb3993 |
@ -1733,46 +1733,51 @@ fn format_fields(
|
|||||||
// select the attributes to retrieve
|
// select the attributes to retrieve
|
||||||
let displayable_names =
|
let displayable_names =
|
||||||
displayable_ids.iter().map(|&fid| field_ids_map.name(fid).expect("Missing field name"));
|
displayable_ids.iter().map(|&fid| field_ids_map.name(fid).expect("Missing field name"));
|
||||||
permissive_json_pointer::map_leaf_values(&mut document, displayable_names, |key, value| {
|
permissive_json_pointer::map_leaf_values(
|
||||||
// To get the formatting option of each key we need to see all the rules that applies
|
&mut document,
|
||||||
// to the value and merge them together. eg. If a user said he wanted to highlight `doggo`
|
displayable_names,
|
||||||
// and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only
|
|key, array_indices, value| {
|
||||||
// highlighted.
|
// To get the formatting option of each key we need to see all the rules that applies
|
||||||
// Warn: The time to compute the format list scales with the number of fields to format;
|
// to the value and merge them together. eg. If a user said he wanted to highlight `doggo`
|
||||||
// cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity:
|
// and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only
|
||||||
// d*f where d is the total number of fields to display and f is the total number of fields to format.
|
// highlighted.
|
||||||
let format = formatting_fields_options
|
// Warn: The time to compute the format list scales with the number of fields to format;
|
||||||
.iter()
|
// cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity:
|
||||||
.filter(|(name, _option)| {
|
// d*f where d is the total number of fields to display and f is the total number of fields to format.
|
||||||
milli::is_faceted_by(name, key) || milli::is_faceted_by(key, name)
|
let format = formatting_fields_options
|
||||||
})
|
|
||||||
.map(|(_, option)| **option)
|
|
||||||
.reduce(|acc, option| acc.merge(option));
|
|
||||||
let mut infos = Vec::new();
|
|
||||||
|
|
||||||
// if no locales has been provided, we try to find the locales in the localized_attributes.
|
|
||||||
let locales = locales.or_else(|| {
|
|
||||||
localized_attributes
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|rule| rule.match_str(key))
|
.filter(|(name, _option)| {
|
||||||
.map(LocalizedAttributesRule::locales)
|
milli::is_faceted_by(name, key) || milli::is_faceted_by(key, name)
|
||||||
});
|
})
|
||||||
|
.map(|(_, option)| **option)
|
||||||
|
.reduce(|acc, option| acc.merge(option));
|
||||||
|
let mut infos = Vec::new();
|
||||||
|
|
||||||
*value = format_value(
|
// if no locales has been provided, we try to find the locales in the localized_attributes.
|
||||||
std::mem::take(value),
|
let locales = locales.or_else(|| {
|
||||||
builder,
|
localized_attributes
|
||||||
format,
|
.iter()
|
||||||
&mut infos,
|
.find(|rule| rule.match_str(key))
|
||||||
compute_matches,
|
.map(LocalizedAttributesRule::locales)
|
||||||
locales,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(matches) = matches_position.as_mut() {
|
*value = format_value(
|
||||||
if !infos.is_empty() {
|
std::mem::take(value),
|
||||||
matches.insert(key.to_owned(), infos);
|
builder,
|
||||||
|
format,
|
||||||
|
&mut infos,
|
||||||
|
compute_matches,
|
||||||
|
array_indices,
|
||||||
|
locales,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(matches) = matches_position.as_mut() {
|
||||||
|
if !infos.is_empty() {
|
||||||
|
matches.insert(key.to_owned(), infos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
let selectors = formatted_options
|
let selectors = formatted_options
|
||||||
.keys()
|
.keys()
|
||||||
@ -1790,13 +1795,14 @@ fn format_value(
|
|||||||
format_options: Option<FormatOptions>,
|
format_options: Option<FormatOptions>,
|
||||||
infos: &mut Vec<MatchBounds>,
|
infos: &mut Vec<MatchBounds>,
|
||||||
compute_matches: bool,
|
compute_matches: bool,
|
||||||
|
array_indices: &[usize],
|
||||||
locales: Option<&[Language]>,
|
locales: Option<&[Language]>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match value {
|
match value {
|
||||||
Value::String(old_string) => {
|
Value::String(old_string) => {
|
||||||
let mut matcher = builder.build(&old_string, locales);
|
let mut matcher = builder.build(&old_string, locales);
|
||||||
if compute_matches {
|
if compute_matches {
|
||||||
let matches = matcher.matches();
|
let matches = matcher.matches(array_indices);
|
||||||
infos.extend_from_slice(&matches[..]);
|
infos.extend_from_slice(&matches[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1808,51 +1814,15 @@ fn format_value(
|
|||||||
None => Value::String(old_string),
|
None => Value::String(old_string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Array(values) => Value::Array(
|
// `map_leaf_values` makes sure this is only called for leaf fields
|
||||||
values
|
Value::Array(_) => unreachable!(),
|
||||||
.into_iter()
|
Value::Object(_) => unreachable!(),
|
||||||
.map(|v| {
|
|
||||||
format_value(
|
|
||||||
v,
|
|
||||||
builder,
|
|
||||||
format_options.map(|format_options| FormatOptions {
|
|
||||||
highlight: format_options.highlight,
|
|
||||||
crop: None,
|
|
||||||
}),
|
|
||||||
infos,
|
|
||||||
compute_matches,
|
|
||||||
locales,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Value::Object(object) => Value::Object(
|
|
||||||
object
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
(
|
|
||||||
k,
|
|
||||||
format_value(
|
|
||||||
v,
|
|
||||||
builder,
|
|
||||||
format_options.map(|format_options| FormatOptions {
|
|
||||||
highlight: format_options.highlight,
|
|
||||||
crop: None,
|
|
||||||
}),
|
|
||||||
infos,
|
|
||||||
compute_matches,
|
|
||||||
locales,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Value::Number(number) => {
|
Value::Number(number) => {
|
||||||
let s = number.to_string();
|
let s = number.to_string();
|
||||||
|
|
||||||
let mut matcher = builder.build(&s, locales);
|
let mut matcher = builder.build(&s, locales);
|
||||||
if compute_matches {
|
if compute_matches {
|
||||||
let matches = matcher.matches();
|
let matches = matcher.matches(array_indices);
|
||||||
infos.extend_from_slice(&matches[..]);
|
infos.extend_from_slice(&matches[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +208,10 @@ async fn format_nested() {
|
|||||||
"doggos.name": [
|
"doggos.name": [
|
||||||
{
|
{
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"length": 5
|
"length": 5,
|
||||||
|
"indices": [
|
||||||
|
0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,8 @@ impl FormatOptions {
|
|||||||
pub struct MatchBounds {
|
pub struct MatchBounds {
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub indices: Option<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure used to analyze a string, compute words that match,
|
/// Structure used to analyze a string, compute words that match,
|
||||||
@ -220,15 +222,20 @@ impl<'t, 'tokenizer> Matcher<'t, 'tokenizer, '_, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns boundaries of the words that match the query.
|
/// Returns boundaries of the words that match the query.
|
||||||
pub fn matches(&mut self) -> Vec<MatchBounds> {
|
pub fn matches(&mut self, array_indices: &[usize]) -> Vec<MatchBounds> {
|
||||||
match &self.matches {
|
match &self.matches {
|
||||||
None => self.compute_matches().matches(),
|
None => self.compute_matches().matches(array_indices),
|
||||||
Some((tokens, matches)) => matches
|
Some((tokens, matches)) => matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| MatchBounds {
|
.map(|m| MatchBounds {
|
||||||
start: tokens[m.get_first_token_pos()].byte_start,
|
start: tokens[m.get_first_token_pos()].byte_start,
|
||||||
// TODO: Why is this in chars, while start is in bytes?
|
// TODO: Why is this in chars, while start is in bytes?
|
||||||
length: m.char_count,
|
length: m.char_count,
|
||||||
|
indices: if array_indices.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(array_indices.to_owned())
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ fn contained_in(selector: &str, key: &str) -> bool {
|
|||||||
/// map_leaf_values(
|
/// map_leaf_values(
|
||||||
/// value.as_object_mut().unwrap(),
|
/// value.as_object_mut().unwrap(),
|
||||||
/// ["jean.race.name"],
|
/// ["jean.race.name"],
|
||||||
/// |key, value| match (value, key) {
|
/// |key, _array_indices, value| match (value, key) {
|
||||||
/// (Value::String(name), "jean.race.name") => *name = "patou".to_string(),
|
/// (Value::String(name), "jean.race.name") => *name = "patou".to_string(),
|
||||||
/// _ => unreachable!(),
|
/// _ => unreachable!(),
|
||||||
/// },
|
/// },
|
||||||
@ -66,17 +66,18 @@ fn contained_in(selector: &str, key: &str) -> bool {
|
|||||||
pub fn map_leaf_values<'a>(
|
pub fn map_leaf_values<'a>(
|
||||||
value: &mut Map<String, Value>,
|
value: &mut Map<String, Value>,
|
||||||
selectors: impl IntoIterator<Item = &'a str>,
|
selectors: impl IntoIterator<Item = &'a str>,
|
||||||
mut mapper: impl FnMut(&str, &mut Value),
|
mut mapper: impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
let selectors: Vec<_> = selectors.into_iter().collect();
|
let selectors: Vec<_> = selectors.into_iter().collect();
|
||||||
map_leaf_values_in_object(value, &selectors, "", &mut mapper);
|
map_leaf_values_in_object(value, &selectors, "", &[], &mut mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_leaf_values_in_object(
|
pub fn map_leaf_values_in_object(
|
||||||
value: &mut Map<String, Value>,
|
value: &mut Map<String, Value>,
|
||||||
selectors: &[&str],
|
selectors: &[&str],
|
||||||
base_key: &str,
|
base_key: &str,
|
||||||
mapper: &mut impl FnMut(&str, &mut Value),
|
array_indices: &[usize],
|
||||||
|
mapper: &mut impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
for (key, value) in value.iter_mut() {
|
for (key, value) in value.iter_mut() {
|
||||||
let base_key = if base_key.is_empty() {
|
let base_key = if base_key.is_empty() {
|
||||||
@ -94,12 +95,12 @@ pub fn map_leaf_values_in_object(
|
|||||||
if should_continue {
|
if should_continue {
|
||||||
match value {
|
match value {
|
||||||
Value::Object(object) => {
|
Value::Object(object) => {
|
||||||
map_leaf_values_in_object(object, selectors, &base_key, mapper)
|
map_leaf_values_in_object(object, selectors, &base_key, array_indices, mapper)
|
||||||
}
|
}
|
||||||
Value::Array(array) => {
|
Value::Array(array) => {
|
||||||
map_leaf_values_in_array(array, selectors, &base_key, mapper)
|
map_leaf_values_in_array(array, selectors, &base_key, array_indices, mapper)
|
||||||
}
|
}
|
||||||
value => mapper(&base_key, value),
|
value => mapper(&base_key, array_indices, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,13 +110,24 @@ pub fn map_leaf_values_in_array(
|
|||||||
values: &mut [Value],
|
values: &mut [Value],
|
||||||
selectors: &[&str],
|
selectors: &[&str],
|
||||||
base_key: &str,
|
base_key: &str,
|
||||||
mapper: &mut impl FnMut(&str, &mut Value),
|
base_array_indices: &[usize],
|
||||||
|
mapper: &mut impl FnMut(&str, &[usize], &mut Value),
|
||||||
) {
|
) {
|
||||||
for value in values.iter_mut() {
|
// This avoids allocating twice
|
||||||
|
let mut array_indices = Vec::with_capacity(base_array_indices.len() + 1);
|
||||||
|
array_indices.extend_from_slice(base_array_indices);
|
||||||
|
array_indices.push(0);
|
||||||
|
|
||||||
|
for (i, value) in values.iter_mut().enumerate() {
|
||||||
|
*array_indices.last_mut().unwrap() = i;
|
||||||
match value {
|
match value {
|
||||||
Value::Object(object) => map_leaf_values_in_object(object, selectors, base_key, mapper),
|
Value::Object(object) => {
|
||||||
Value::Array(array) => map_leaf_values_in_array(array, selectors, base_key, mapper),
|
map_leaf_values_in_object(object, selectors, base_key, &array_indices, mapper)
|
||||||
value => mapper(base_key, value),
|
}
|
||||||
|
Value::Array(array) => {
|
||||||
|
map_leaf_values_in_array(array, selectors, base_key, &array_indices, mapper)
|
||||||
|
}
|
||||||
|
value => mapper(base_key, &array_indices, value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -743,12 +755,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
map_leaf_values(value.as_object_mut().unwrap(), ["jean.race.name"], |key, value| {
|
map_leaf_values(
|
||||||
match (value, key) {
|
value.as_object_mut().unwrap(),
|
||||||
|
["jean.race.name"],
|
||||||
|
|key, _, value| match (value, key) {
|
||||||
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
@ -775,7 +789,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut calls = 0;
|
let mut calls = 0;
|
||||||
map_leaf_values(value.as_object_mut().unwrap(), ["jean"], |key, value| {
|
map_leaf_values(value.as_object_mut().unwrap(), ["jean"], |key, _, value| {
|
||||||
calls += 1;
|
calls += 1;
|
||||||
match (value, key) {
|
match (value, key) {
|
||||||
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
(Value::String(name), "jean.race.name") => *name = S("patou"),
|
||||||
@ -798,4 +812,52 @@ mod tests {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_array() {
|
||||||
|
let mut value: Value = json!({
|
||||||
|
"no_array": "peter",
|
||||||
|
"simple": ["foo", "bar"],
|
||||||
|
"nested": [
|
||||||
|
{
|
||||||
|
"a": [
|
||||||
|
["cat", "dog"],
|
||||||
|
["fox", "bear"],
|
||||||
|
],
|
||||||
|
"b": "hi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a": ["green", "blue"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
map_leaf_values(
|
||||||
|
value.as_object_mut().unwrap(),
|
||||||
|
["no_array", "simple", "nested"],
|
||||||
|
|_key, array_indices, value| {
|
||||||
|
*value = format!("{array_indices:?}").into();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
value,
|
||||||
|
json!({
|
||||||
|
"no_array": "[]",
|
||||||
|
"simple": ["[0]", "[1]"],
|
||||||
|
"nested": [
|
||||||
|
{
|
||||||
|
"a": [
|
||||||
|
["[0, 0, 0]", "[0, 0, 1]"],
|
||||||
|
["[0, 1, 0]", "[0, 1, 1]"],
|
||||||
|
],
|
||||||
|
"b": "[0]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a": ["[1, 0]", "[1, 1]"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user