From c12fee2f7b3844920e6b52b3da67dc19984b8b5b Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:40:02 +0100 Subject: [PATCH] fix author query --- .../search/backend/elasticsearch/filter.rs | 21 ++++++++++++++----- .../backend/elasticsearch/typesense_parity.rs | 4 ++++ apps/labrinth/src/search/indexing.rs | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/labrinth/src/search/backend/elasticsearch/filter.rs b/apps/labrinth/src/search/backend/elasticsearch/filter.rs index 8849315eb4..0134c34cb2 100644 --- a/apps/labrinth/src/search/backend/elasticsearch/filter.rs +++ b/apps/labrinth/src/search/backend/elasticsearch/filter.rs @@ -5,6 +5,7 @@ use crate::search::filter::{ FilterComparison, FilterCondition, FilterExpr, FilterLiteral, FilterPredicate, }; +use crate::search::indexing::normalize_for_search; const MAX_DNF_CLAUSES: usize = 64; const MAX_FILTER_DEPTH: usize = 64; @@ -146,10 +147,11 @@ fn lower(filter: &FilterExpr) -> Result { } fn predicate_query(predicate: &FilterPredicate) -> Result { - let field = exact_field(predicate.field.as_str()); + let source_field = predicate.field.as_str(); + let field = exact_field(source_field); match &predicate.condition { FilterCondition::Compare { comparison, value } => { - let value = literal_value(value)?; + let value = literal_value(source_field, value)?; Ok(match comparison { FilterComparison::Equal => { json!({"term": {(field): {"value": value}}}) @@ -174,7 +176,7 @@ fn predicate_query(predicate: &FilterPredicate) -> Result { FilterCondition::In { values, negated } => { let values = values .iter() - .map(literal_value) + .map(|value| literal_value(source_field, value)) .collect::>>()?; let query = json!({"terms": {(field): values}}); Ok(if *negated { not_query(query) } else { query }) @@ -186,8 +188,11 @@ fn predicate_query(predicate: &FilterPredicate) -> Result { } } -fn literal_value(literal: &FilterLiteral) -> Result { +fn literal_value(field: &str, literal: &FilterLiteral) -> Result { match literal { + FilterLiteral::String(value) if field == "author" => { + Ok(Value::String(normalize_for_search(value))) + } FilterLiteral::String(value) => Ok(Value::String(value.clone())), FilterLiteral::Number(value) => serde_json::from_str(value) .map_err(|error| eyre!("invalid numeric filter literal: {error}")), @@ -198,7 +203,7 @@ fn literal_value(literal: &FilterLiteral) -> Result { fn exact_field(field: &str) -> &str { match field { "name" => "name.keyword", - "author" => "author.keyword", + "author" => "indexed_author.keyword", "summary" => "summary.keyword", "slug" => "slug.keyword", _ => field, @@ -384,6 +389,12 @@ mod tests { assert_eq!(query["term"]["license"]["value"], "MIT"); } + #[test] + fn author_filters_use_the_normalized_exact_field() { + let query = serialize("author = User"); + assert_eq!(query["term"]["indexed_author.keyword"]["value"], "user"); + } + #[test] fn mixed_boolean_filters_preserve_version_correlation() { let query = serialize( diff --git a/apps/labrinth/src/search/backend/elasticsearch/typesense_parity.rs b/apps/labrinth/src/search/backend/elasticsearch/typesense_parity.rs index 17cbc41235..8da91481b0 100644 --- a/apps/labrinth/src/search/backend/elasticsearch/typesense_parity.rs +++ b/apps/labrinth/src/search/backend/elasticsearch/typesense_parity.rs @@ -2008,6 +2008,10 @@ impl Elasticsearch { "max_chars": 10 }, "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 8191 + }, "prefix": { "type": "text", "analyzer": "typesense_plain_prefix_text", diff --git a/apps/labrinth/src/search/indexing.rs b/apps/labrinth/src/search/indexing.rs index 5180bfea9c..03e6c34061 100644 --- a/apps/labrinth/src/search/indexing.rs +++ b/apps/labrinth/src/search/indexing.rs @@ -45,7 +45,7 @@ struct PartialProject { components: exp::ProjectSerial, } -fn normalize_for_search(s: &str) -> String { +pub(crate) fn normalize_for_search(s: &str) -> String { static SPECIAL_CHARS_RE: LazyLock = LazyLock::new(|| Regex::new(r"[^a-zA-Z0-9-.\s]").expect("valid regex"));