Fix search with best guess query containing hyphen (#1753)

Split searched best guess query into single wildcard query terms.
This commit is contained in:
Sebastian Sdorra
2021-08-02 13:43:48 +02:00
committed by GitHub
parent 124e7f1b18
commit 6a5d56244c
3 changed files with 67 additions and 10 deletions

View File

@@ -0,0 +1,2 @@
- type: Fixed
description: Fixed search queries containing hypens ([#1743](https://github.com/scm-manager/scm-manager/issues/1743) and [#1753](https://github.com/scm-manager/scm-manager/pull/1753))

View File

@@ -26,6 +26,8 @@ package sonia.scm.search;
import com.google.common.base.Strings;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
@@ -130,7 +132,7 @@ public class LuceneQueryBuilder extends QueryBuilder {
return createExpertQuery(searchableType, queryParams);
}
return createBestGuessQuery(searchableType, queryParams);
} catch (QueryNodeException ex) {
} catch (QueryNodeException | IOException ex) {
throw new QueryParseException(queryString, "failed to parse query", ex);
}
}
@@ -142,15 +144,25 @@ public class LuceneQueryBuilder extends QueryBuilder {
return parser.parse(queryParams.getQueryString(), "");
}
public Query createBestGuessQuery(LuceneSearchableType searchableType, QueryBuilder.QueryParams queryParams) {
public Query createBestGuessQuery(LuceneSearchableType searchableType, QueryBuilder.QueryParams queryParams) throws QueryNodeException, IOException {
String[] fieldNames = searchableType.getFieldNames();
if (fieldNames == null || fieldNames.length == 0) {
throw new NoDefaultQueryFieldsFoundException(searchableType.getType());
}
String queryString = queryParams.getQueryString().toLowerCase(Locale.ENGLISH);
boolean hasWildcard = containsWildcard(queryString);
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (String fieldName : fieldNames) {
Term term = new Term(fieldName, appendWildcardIfNotAlreadyUsed(queryParams));
WildcardQuery query = new WildcardQuery(term);
Query query;
if (!hasWildcard) {
query = createWildcardQuery(fieldName, queryString);
} else {
StandardQueryParser parser = new StandardQueryParser(analyzer);
parser.setPointsConfigMap(searchableType.getPointsConfig());
query = parser.parse(queryParams.getQueryString(), fieldName);
}
Float boost = searchableType.getBoosts().get(fieldName);
if (boost != null) {
@@ -162,13 +174,24 @@ public class LuceneQueryBuilder extends QueryBuilder {
return builder.build();
}
@Nonnull
private String appendWildcardIfNotAlreadyUsed(QueryParams queryParams) {
String queryString = queryParams.getQueryString().toLowerCase(Locale.ENGLISH);
if (!queryString.contains("?") && !queryString.contains("*")) {
queryString += "*";
private Query createWildcardQuery(String fieldName, String queryString) throws IOException {
try (TokenStream tokenStream = analyzer.tokenStream(fieldName, queryString)) {
CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);
BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder();
tokenStream.reset();
while (tokenStream.incrementToken()) {
String token = attribute.toString();
queryBuilder.add(new WildcardQuery(new Term(fieldName, token + "*")), BooleanClause.Occur.SHOULD);
}
return queryBuilder.build();
}
return queryString;
}
private boolean containsWildcard(String queryString) {
return queryString.contains("?") || queryString.contains("*");
}
@FunctionalInterface

View File

@@ -63,6 +63,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
@SuppressWarnings("java:S5976")
@SubjectAware(value = "trillian", permissions = "abc")
@ExtendWith({MockitoExtension.class, ShiroExtension.class})
class LuceneQueryBuilderTest {
@@ -107,6 +108,37 @@ class LuceneQueryBuilderTest {
assertThat(result.getTotalHits()).isOne();
}
@Test
void shouldMatchWithHyphen() throws IOException {
try (IndexWriter writer = writer()) {
writer.addDocument(personDoc("Trillian-McMillan"));
}
QueryResult result = query(Person.class, "Trillian-McMi");
assertThat(result.getTotalHits()).isOne();
}
@Test
void shouldMatchQueryWithMultipleFieldsAndHyphen() throws IOException {
try (IndexWriter writer = writer()) {
writer.addDocument(inetOrgPersonDoc("Trillian", "McMillan", "Trillian McMillan", "abc"));
}
QueryResult result = query(InetOrgPerson.class, "Trillian-McMi");
assertThat(result.getTotalHits()).isOne();
}
@Test
void shouldMatchExpertQueryWithHyphen() throws IOException {
try (IndexWriter writer = writer()) {
writer.addDocument(personDoc("Trillian-McMillan"));
}
QueryResult result = query(Person.class, "lastName:Trillian-McMi");
assertThat(result.getTotalHits()).isOne();
}
@Test
@SuppressWarnings("java:S5976")
void shouldNotAppendWildcardIfStarIsUsed() throws IOException {