Skip to content

Commit ce1ea3e

Browse files
authored
Merge branch 'main' into fix-for-issue-JabRef#372
2 parents 9fdbbb4 + 1bb4e39 commit ce1ea3e

File tree

15 files changed

+305
-302
lines changed

15 files changed

+305
-302
lines changed

src/main/java/org/jabref/gui/entryeditor/SourceTab.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,16 @@ public SourceTab(BibDatabaseContext bibDatabaseContext,
105105
}
106106

107107
private void highlightSearchPattern() {
108-
if (codeArea == null || searchQueryProperty.get().isEmpty()) {
108+
if (codeArea == null) {
109109
return;
110110
}
111111

112112
codeArea.setStyleClass(0, codeArea.getLength(), TEXT_STYLE);
113-
Map<Optional<Field>, List<String>> searchTermsMap = Highlighter.groupTermsByField(searchQueryProperty.get().get());
113+
if (searchQueryProperty.get().isEmpty()) {
114+
return;
115+
}
114116

117+
Map<Optional<Field>, List<String>> searchTermsMap = Highlighter.groupTermsByField(searchQueryProperty.get().get());
115118
searchTermsMap.forEach((optionalField, terms) -> {
116119
Optional<String> searchPattern = Highlighter.buildSearchPattern(terms);
117120
if (searchPattern.isEmpty()) {

src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ protected void bindToEntry(BibEntry entry) {
9090
if (entry == null || !shouldShow(entry)) {
9191
return;
9292
}
93-
if (documentViewerView == null) {
94-
documentViewerView = new DocumentViewerView();
95-
}
9693
this.entry = entry;
9794
content.getChildren().clear();
9895

@@ -159,6 +156,9 @@ private Text createPageLink(LinkedFile linkedFile, int pageNumber) {
159156

160157
pageLink.setOnMouseClicked(event -> {
161158
if (MouseButton.PRIMARY == event.getButton()) {
159+
if (documentViewerView == null) {
160+
documentViewerView = new DocumentViewerView();
161+
}
162162
documentViewerView.switchToFile(linkedFile);
163163
documentViewerView.gotoPage(pageNumber);
164164
documentViewerView.disableLiveMode();

src/main/java/org/jabref/logic/search/PostgreServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public PostgreServer() {
2323
EmbeddedPostgres embeddedPostgres;
2424
try {
2525
embeddedPostgres = EmbeddedPostgres.builder()
26+
.setOutputRedirector(ProcessBuilder.Redirect.DISCARD)
2627
.start();
2728
LOGGER.info("Postgres server started, connection port: {}", embeddedPostgres.getPort());
2829
} catch (IOException e) {

src/main/java/org/jabref/logic/search/indexing/BibFieldsIndexer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.jabref.model.entry.field.StandardField;
2424
import org.jabref.model.search.PostgreConstants;
2525

26+
import io.github.thibaultmeyer.cuid.CUID;
2627
import org.slf4j.Logger;
2728
import org.slf4j.LoggerFactory;
2829

@@ -53,7 +54,7 @@ public BibFieldsIndexer(BibEntryPreferences bibEntryPreferences, BibDatabaseCont
5354
this.keywordSeparator = bibEntryPreferences.getKeywordSeparator();
5455
this.libraryName = databaseContext.getDatabasePath().map(path -> path.getFileName().toString()).orElse("unsaved");
5556

56-
this.mainTable = databaseContext.getUniqueName();
57+
this.mainTable = CUID.randomCUID2(12).toString();
5758
this.splitValuesTable = mainTable + SPLIT_TABLE_SUFFIX;
5859

5960
this.schemaMainTableReference = PostgreConstants.getMainTableSchemaReference(mainTable);

src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import org.jabref.model.search.query.SqlQueryNode;
88
import org.jabref.search.SearchParser;
99

10-
import org.apache.lucene.search.Query;
1110
import org.slf4j.Logger;
1211
import org.slf4j.LoggerFactory;
1312

@@ -24,9 +23,9 @@ public static String flagsToSearchExpression(SearchQuery searchQuery) {
2423
return new SearchFlagsToExpressionVisitor(searchQuery.getSearchFlags()).visit(searchQuery.getContext());
2524
}
2625

27-
public static Query searchToLucene(SearchQuery searchQuery) {
26+
public static String searchToLucene(SearchQuery searchQuery) {
2827
LOGGER.debug("Converting search expression to Lucene: {}", searchQuery.getSearchExpression());
29-
return new SearchToLuceneVisitor().visit(searchQuery.getContext());
28+
return new SearchToLuceneVisitor(searchQuery.getSearchFlags()).visit(searchQuery.getContext());
3029
}
3130

3231
public static List<SearchQueryNode> extractSearchTerms(SearchQuery searchQuery) {
Lines changed: 74 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,131 @@
11
package org.jabref.logic.search.query;
22

3+
import java.util.EnumSet;
34
import java.util.List;
5+
import java.util.Locale;
46

57
import org.jabref.model.search.LinkedFilesConstants;
8+
import org.jabref.model.search.SearchFlags;
69
import org.jabref.search.SearchBaseVisitor;
710
import org.jabref.search.SearchParser;
811

9-
import org.apache.lucene.index.Term;
10-
import org.apache.lucene.search.BooleanClause;
11-
import org.apache.lucene.search.BooleanQuery;
12-
import org.apache.lucene.search.MatchNoDocsQuery;
13-
import org.apache.lucene.search.Query;
14-
import org.apache.lucene.search.RegexpQuery;
15-
import org.apache.lucene.search.TermQuery;
16-
import org.apache.lucene.util.QueryBuilder;
12+
import org.apache.lucene.queryparser.classic.QueryParser;
1713

1814
/**
1915
* Tests are located in {@link org.jabref.logic.search.query.SearchQueryLuceneConversionTest}.
2016
*/
21-
public class SearchToLuceneVisitor extends SearchBaseVisitor<Query> {
17+
public class SearchToLuceneVisitor extends SearchBaseVisitor<String> {
18+
private final EnumSet<SearchFlags> searchFlags;
2219

23-
private static final List<String> SEARCH_FIELDS = LinkedFilesConstants.PDF_FIELDS;
24-
25-
private final QueryBuilder queryBuilder;
26-
27-
public SearchToLuceneVisitor() {
28-
this.queryBuilder = new QueryBuilder(LinkedFilesConstants.LINKED_FILES_ANALYZER);
20+
public SearchToLuceneVisitor(EnumSet<SearchFlags> searchFlags) {
21+
this.searchFlags = searchFlags;
2922
}
3023

3124
@Override
32-
public Query visitStart(SearchParser.StartContext ctx) {
25+
public String visitStart(SearchParser.StartContext ctx) {
3326
return visit(ctx.andExpression());
3427
}
3528

3629
@Override
37-
public Query visitImplicitAndExpression(SearchParser.ImplicitAndExpressionContext ctx) {
38-
List<Query> children = ctx.expression().stream().map(this::visit).toList();
39-
if (children.size() == 1) {
40-
return children.getFirst();
41-
}
42-
BooleanQuery.Builder builder = new BooleanQuery.Builder();
43-
for (Query child : children) {
44-
builder.add(child, BooleanClause.Occur.MUST);
45-
}
46-
return builder.build();
30+
public String visitImplicitAndExpression(SearchParser.ImplicitAndExpressionContext ctx) {
31+
List<String> children = ctx.expression().stream().map(this::visit).toList();
32+
return children.size() == 1 ? children.getFirst() : String.join(" ", children);
4733
}
4834

4935
@Override
50-
public Query visitParenExpression(SearchParser.ParenExpressionContext ctx) {
51-
return visit(ctx.andExpression());
36+
public String visitParenExpression(SearchParser.ParenExpressionContext ctx) {
37+
String expr = visit(ctx.andExpression());
38+
return expr.isEmpty() ? "" : "(" + expr + ")";
5239
}
5340

5441
@Override
55-
public Query visitNegatedExpression(SearchParser.NegatedExpressionContext ctx) {
56-
Query innerQuery = visit(ctx.expression());
57-
if (innerQuery instanceof MatchNoDocsQuery) {
58-
return innerQuery;
59-
}
60-
BooleanQuery.Builder builder = new BooleanQuery.Builder();
61-
builder.add(innerQuery, BooleanClause.Occur.MUST_NOT);
62-
return builder.build();
42+
public String visitNegatedExpression(SearchParser.NegatedExpressionContext ctx) {
43+
return "NOT (" + visit(ctx.expression()) + ")";
6344
}
6445

6546
@Override
66-
public Query visitBinaryExpression(SearchParser.BinaryExpressionContext ctx) {
67-
Query left = visit(ctx.left);
68-
Query right = visit(ctx.right);
47+
public String visitBinaryExpression(SearchParser.BinaryExpressionContext ctx) {
48+
String left = visit(ctx.left);
49+
String right = visit(ctx.right);
6950

70-
if (left instanceof MatchNoDocsQuery) {
51+
if (left.isEmpty() && right.isEmpty()) {
52+
return "";
53+
}
54+
if (left.isEmpty()) {
7155
return right;
7256
}
73-
if (right instanceof MatchNoDocsQuery) {
57+
if (right.isEmpty()) {
7458
return left;
7559
}
7660

77-
BooleanQuery.Builder builder = new BooleanQuery.Builder();
61+
String operator = ctx.bin_op.getType() == SearchParser.AND ? " AND " : " OR ";
62+
return left + operator + right;
63+
}
64+
65+
@Override
66+
public String visitComparison(SearchParser.ComparisonContext ctx) {
67+
String term = SearchQueryConversion.unescapeSearchValue(ctx.searchValue());
68+
boolean isQuoted = ctx.searchValue().getStart().getType() == SearchParser.STRING_LITERAL;
69+
70+
// unfielded expression
71+
if (ctx.FIELD() == null) {
72+
if (searchFlags.contains(SearchFlags.REGULAR_EXPRESSION)) {
73+
return "/" + term + "/";
74+
}
75+
return isQuoted ? "\"" + escapeQuotes(term) + "\"" : QueryParser.escape(term);
76+
}
7877

79-
if (ctx.bin_op.getType() == SearchParser.AND) {
80-
builder.add(left, BooleanClause.Occur.MUST);
81-
builder.add(right, BooleanClause.Occur.MUST);
82-
} else if (ctx.bin_op.getType() == SearchParser.OR) {
83-
builder.add(left, BooleanClause.Occur.SHOULD);
84-
builder.add(right, BooleanClause.Occur.SHOULD);
78+
String field = ctx.FIELD().getText().toLowerCase(Locale.ROOT);
79+
if (!isValidField(field)) {
80+
return "";
8581
}
8682

87-
return builder.build();
83+
field = "any".equals(field) || "anyfield".equals(field) ? "" : field + ":";
84+
int operator = ctx.operator().getStart().getType();
85+
return buildFieldExpression(field, term, operator, isQuoted);
8886
}
8987

90-
@Override
91-
public Query visitComparisonExpression(SearchParser.ComparisonExpressionContext ctx) {
92-
return visit(ctx.comparison());
88+
private boolean isValidField(String field) {
89+
return "any".equals(field) || "anyfield".equals(field) || LinkedFilesConstants.PDF_FIELDS.contains(field);
9390
}
9491

95-
@Override
96-
public Query visitComparison(SearchParser.ComparisonContext ctx) {
97-
String field = ctx.FIELD() == null ? null : ctx.FIELD().getText();
98-
String term = SearchQueryConversion.unescapeSearchValue(ctx.searchValue());
92+
private String buildFieldExpression(String field, String term, int operator, boolean isQuoted) {
93+
boolean isRegexOp = isRegexOperator(operator);
94+
boolean isNegationOp = isNegationOperator(operator);
9995

100-
// unfielded expression
101-
if (field == null || "anyfield".equals(field) || "any".equals(field)) {
102-
return createMultiFieldQuery(term, ctx.operator());
103-
} else if (SEARCH_FIELDS.contains(field)) {
104-
return createFieldQuery(field, term, ctx.operator());
96+
if (isRegexOp) {
97+
String expression = field + "/" + term + "/";
98+
return isNegationOp ? "NOT " + expression : expression;
10599
} else {
106-
return new MatchNoDocsQuery();
100+
term = isQuoted ? "\"" + escapeQuotes(term) + "\"" : QueryParser.escape(term);
101+
String expression = field + term;
102+
return isNegationOp ? "NOT " + expression : expression;
107103
}
108104
}
109105

110-
private Query createMultiFieldQuery(String value, SearchParser.OperatorContext operator) {
111-
BooleanQuery.Builder builder = new BooleanQuery.Builder();
112-
for (String field : SEARCH_FIELDS) {
113-
builder.add(createFieldQuery(field, value, operator), BooleanClause.Occur.SHOULD);
114-
}
115-
return builder.build();
106+
private static String escapeQuotes(String term) {
107+
return term.replace("\"", "\\\"");
116108
}
117109

118-
private Query createFieldQuery(String field, String value, SearchParser.OperatorContext operator) {
119-
if (operator == null) {
120-
return createTermOrPhraseQuery(field, value);
121-
}
122-
123-
return switch (operator.getStart().getType()) {
124-
case SearchParser.REQUAL,
125-
SearchParser.CREEQUAL ->
126-
new RegexpQuery(new Term(field, value));
110+
private static boolean isNegationOperator(int operator) {
111+
return switch (operator) {
127112
case SearchParser.NEQUAL,
128113
SearchParser.NCEQUAL,
129114
SearchParser.NEEQUAL,
130-
SearchParser.NCEEQUAL ->
131-
createNegatedQuery(createTermOrPhraseQuery(field, value));
132-
case SearchParser.NREQUAL,
133-
SearchParser.NCREEQUAL ->
134-
createNegatedQuery(new RegexpQuery(new Term(field, value)));
135-
default ->
136-
createTermOrPhraseQuery(field, value);
115+
SearchParser.NCEEQUAL,
116+
SearchParser.NREQUAL,
117+
SearchParser.NCREEQUAL -> true;
118+
default -> false;
137119
};
138120
}
139121

140-
private Query createNegatedQuery(Query query) {
141-
BooleanQuery.Builder negatedQuery = new BooleanQuery.Builder();
142-
negatedQuery.add(query, BooleanClause.Occur.MUST_NOT);
143-
return negatedQuery.build();
144-
}
145-
146-
private Query createTermOrPhraseQuery(String field, String value) {
147-
if (value.contains("*") || value.contains("?")) {
148-
return new TermQuery(new Term(field, value));
149-
}
150-
return queryBuilder.createPhraseQuery(field, value);
122+
private static boolean isRegexOperator(int operator) {
123+
return switch (operator) {
124+
case SearchParser.REQUAL,
125+
SearchParser.CREEQUAL,
126+
SearchParser.NREQUAL,
127+
SearchParser.NCREEQUAL -> true;
128+
default -> false;
129+
};
151130
}
152131
}

src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
import org.apache.lucene.document.Document;
2424
import org.apache.lucene.index.StoredFields;
25+
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
26+
import org.apache.lucene.queryparser.classic.ParseException;
27+
import org.apache.lucene.queryparser.classic.QueryParser;
2528
import org.apache.lucene.search.IndexSearcher;
2629
import org.apache.lucene.search.Query;
2730
import org.apache.lucene.search.ScoreDoc;
@@ -39,29 +42,36 @@ public final class LinkedFilesSearcher {
3942
private final FilePreferences filePreferences;
4043
private final BibDatabaseContext databaseContext;
4144
private final SearcherManager searcherManager;
45+
private final MultiFieldQueryParser parser;
4246

4347
public LinkedFilesSearcher(BibDatabaseContext databaseContext, LuceneIndexer linkedFilesIndexer, FilePreferences filePreferences) {
4448
this.searcherManager = linkedFilesIndexer.getSearcherManager();
4549
this.databaseContext = databaseContext;
4650
this.filePreferences = filePreferences;
51+
this.parser = new MultiFieldQueryParser(LinkedFilesConstants.PDF_FIELDS.toArray(new String[0]), LinkedFilesConstants.LINKED_FILES_ANALYZER);
52+
parser.setDefaultOperator(QueryParser.Operator.AND);
4753
}
4854

4955
public SearchResults search(SearchQuery searchQuery) {
5056
if (!searchQuery.isValid()) {
5157
return new SearchResults();
5258
}
5359

54-
Query luceneQuery = SearchQueryConversion.searchToLucene(searchQuery);
60+
Optional<Query> luceneQuery = getLuceneQuery(searchQuery);
61+
if (luceneQuery.isEmpty()) {
62+
return new SearchResults();
63+
}
64+
5565
EnumSet<SearchFlags> searchFlags = searchQuery.getSearchFlags();
5666
boolean shouldSearchInLinkedFiles = searchFlags.contains(SearchFlags.FULLTEXT) && filePreferences.shouldFulltextIndexLinkedFiles();
5767
if (!shouldSearchInLinkedFiles) {
5868
return new SearchResults();
5969
}
6070

61-
LOGGER.debug("Searching in linked files with query: {}", luceneQuery);
71+
LOGGER.debug("Searching in linked files with query: {}", luceneQuery.get());
6272
try {
6373
IndexSearcher linkedFilesIndexSearcher = acquireIndexSearcher(searcherManager);
64-
SearchResults searchResults = search(linkedFilesIndexSearcher, luceneQuery);
74+
SearchResults searchResults = search(linkedFilesIndexSearcher, luceneQuery.get());
6575
releaseIndexSearcher(searcherManager, linkedFilesIndexSearcher);
6676
return searchResults;
6777
} catch (IOException | IndexSearcher.TooManyClauses e) {
@@ -70,6 +80,16 @@ public SearchResults search(SearchQuery searchQuery) {
7080
return new SearchResults();
7181
}
7282

83+
private Optional<Query> getLuceneQuery(SearchQuery searchQuery) {
84+
String query = SearchQueryConversion.searchToLucene(searchQuery);
85+
try {
86+
return Optional.of(parser.parse(query));
87+
} catch (ParseException e) {
88+
LOGGER.error("Error during query parsing", e);
89+
return Optional.empty();
90+
}
91+
}
92+
7393
private SearchResults search(IndexSearcher indexSearcher, Query searchQuery) throws IOException {
7494
TopDocs topDocs = indexSearcher.search(searchQuery, Integer.MAX_VALUE);
7595
StoredFields storedFields = indexSearcher.storedFields();

0 commit comments

Comments
 (0)