Compare commits

..

1 Commits

Author SHA1 Message Date
Louis Dureuil
5449d59fd2
Merge 3a8051866a into c1d8ee2a8d 2024-11-18 18:08:07 +01:00
4 changed files with 97 additions and 139 deletions

View File

@ -1733,51 +1733,46 @@ 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( permissive_json_pointer::map_leaf_values(&mut document, displayable_names, |key, value| {
&mut document, // To get the formatting option of each key we need to see all the rules that applies
displayable_names, // to the value and merge them together. eg. If a user said he wanted to highlight `doggo`
|key, array_indices, value| { // and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only
// To get the formatting option of each key we need to see all the rules that applies // highlighted.
// to the value and merge them together. eg. If a user said he wanted to highlight `doggo` // Warn: The time to compute the format list scales with the number of fields to format;
// and crop `doggo.name`. `doggo.name` needs to be highlighted + cropped while `doggo.age` is only // cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity:
// highlighted. // d*f where d is the total number of fields to display and f is the total number of fields to format.
// Warn: The time to compute the format list scales with the number of fields to format; let format = formatting_fields_options
// cumulated with map_leaf_values that iterates over all the nested fields, it gives a quadratic complexity: .iter()
// d*f where d is the total number of fields to display and f is the total number of fields to format. .filter(|(name, _option)| {
let format = formatting_fields_options 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();
// 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()
.filter(|(name, _option)| { .find(|rule| rule.match_str(key))
milli::is_faceted_by(name, key) || milli::is_faceted_by(key, name) .map(LocalizedAttributesRule::locales)
}) });
.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. *value = format_value(
let locales = locales.or_else(|| { std::mem::take(value),
localized_attributes builder,
.iter() format,
.find(|rule| rule.match_str(key)) &mut infos,
.map(LocalizedAttributesRule::locales) compute_matches,
}); locales,
);
*value = format_value( if let Some(matches) = matches_position.as_mut() {
std::mem::take(value), if !infos.is_empty() {
builder, matches.insert(key.to_owned(), infos);
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()
@ -1795,14 +1790,13 @@ 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(array_indices); let matches = matcher.matches();
infos.extend_from_slice(&matches[..]); infos.extend_from_slice(&matches[..]);
} }
@ -1814,15 +1808,51 @@ fn format_value(
None => Value::String(old_string), None => Value::String(old_string),
} }
} }
// `map_leaf_values` makes sure this is only called for leaf fields Value::Array(values) => Value::Array(
Value::Array(_) => unreachable!(), values
Value::Object(_) => unreachable!(), .into_iter()
.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(array_indices); let matches = matcher.matches();
infos.extend_from_slice(&matches[..]); infos.extend_from_slice(&matches[..]);
} }

View File

@ -208,10 +208,7 @@ async fn format_nested() {
"doggos.name": [ "doggos.name": [
{ {
"start": 0, "start": 0,
"length": 5, "length": 5
"indices": [
0
]
} }
] ]
} }

View File

@ -105,8 +105,6 @@ 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,
@ -222,20 +220,15 @@ 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, array_indices: &[usize]) -> Vec<MatchBounds> { pub fn matches(&mut self) -> Vec<MatchBounds> {
match &self.matches { match &self.matches {
None => self.compute_matches().matches(array_indices), None => self.compute_matches().matches(),
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(),
} }

View File

@ -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, _array_indices, value| match (value, key) { /// |key, 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,18 +66,17 @@ 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, &[usize], &mut Value), mut mapper: impl FnMut(&str, &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,
array_indices: &[usize], mapper: &mut impl FnMut(&str, &mut Value),
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() {
@ -95,12 +94,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, array_indices, mapper) map_leaf_values_in_object(object, selectors, &base_key, mapper)
} }
Value::Array(array) => { Value::Array(array) => {
map_leaf_values_in_array(array, selectors, &base_key, array_indices, mapper) map_leaf_values_in_array(array, selectors, &base_key, mapper)
} }
value => mapper(&base_key, array_indices, value), value => mapper(&base_key, value),
} }
} }
} }
@ -110,24 +109,13 @@ pub fn map_leaf_values_in_array(
values: &mut [Value], values: &mut [Value],
selectors: &[&str], selectors: &[&str],
base_key: &str, base_key: &str,
base_array_indices: &[usize], mapper: &mut impl FnMut(&str, &mut Value),
mapper: &mut impl FnMut(&str, &[usize], &mut Value),
) { ) {
// This avoids allocating twice for value in values.iter_mut() {
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) => { 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) => map_leaf_values_in_array(array, selectors, base_key, 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),
} }
} }
} }
@ -755,14 +743,12 @@ mod tests {
} }
}); });
map_leaf_values( map_leaf_values(value.as_object_mut().unwrap(), ["jean.race.name"], |key, value| {
value.as_object_mut().unwrap(), match (value, key) {
["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,
@ -789,7 +775,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"),
@ -812,52 +798,4 @@ 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]"],
},
],
})
);
}
} }