Skip to content

Commit eda326b

Browse files
committed
feat(search-engine): Add basic ability to search for user queries
1 parent 3ddf748 commit eda326b

File tree

15 files changed

+317
-13
lines changed

15 files changed

+317
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package se.l4.silo.index.search.internal;
2+
3+
import se.l4.silo.index.search.query.UserQuery;
4+
import se.l4.silo.index.search.query.UserQueryMatcher;
5+
import se.l4.silo.index.search.query.UserQuery.Context;
6+
7+
public class UserQueryMatcherImpl
8+
implements UserQueryMatcher
9+
{
10+
private final UserQuery.Context context;
11+
private final String query;
12+
13+
public UserQueryMatcherImpl(UserQuery.Context context, String query)
14+
{
15+
this.context = context;
16+
this.query = query;
17+
}
18+
19+
@Override
20+
public Context getContext()
21+
{
22+
return context;
23+
}
24+
25+
@Override
26+
public String getQuery()
27+
{
28+
return query;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package se.l4.silo.index.search.query;
2+
3+
import se.l4.silo.index.Matcher;
4+
import se.l4.silo.index.search.internal.UserQueryMatcherImpl;
5+
6+
/**
7+
* Matcher for matching a full text field against a user query.
8+
*/
9+
public interface UserQueryMatcher
10+
extends Matcher<String>
11+
{
12+
/**
13+
* Get the context of the query.
14+
*/
15+
UserQuery.Context getContext();
16+
17+
/**
18+
* Get the query.
19+
*
20+
* @return
21+
*/
22+
String getQuery();
23+
24+
/**
25+
* Create a matcher for a standard query.
26+
*
27+
* @param q
28+
* @return
29+
*/
30+
public static UserQueryMatcher standard(String q)
31+
{
32+
return new UserQueryMatcherImpl(UserQuery.Context.STANDARD, q);
33+
}
34+
35+
/**
36+
* Create a matcher for a type-ahead query.
37+
*
38+
* @param q
39+
* @return
40+
*/
41+
public static UserQueryMatcher typeAhead(String q)
42+
{
43+
return new UserQueryMatcherImpl(UserQuery.Context.TYPE_AHEAD, q);
44+
}
45+
}

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/MappedSearchFieldType.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import se.l4.exobytes.streaming.StreamingOutput;
1111
import se.l4.silo.engine.index.search.SearchFieldDef;
1212
import se.l4.silo.engine.index.search.facets.FacetCollector;
13+
import se.l4.silo.engine.index.search.query.QueryEncounter;
1314
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
1415
import se.l4.silo.engine.index.search.types.SearchFieldType;
1516
import se.l4.silo.index.MappableMatcher;
@@ -71,7 +72,11 @@ public V read(StreamingInput in)
7172

7273
@Override
7374
@SuppressWarnings({ "unchecked", "rawtypes" })
74-
public Query createQuery(String field, Matcher<V> matcher)
75+
public Query createQuery(
76+
QueryEncounter<?> encounter,
77+
String field,
78+
Matcher<V> matcher
79+
)
7580
{
7681
Matcher<T> converted;
7782
if(matcher instanceof MappableMatcher)
@@ -84,7 +89,7 @@ public Query createQuery(String field, Matcher<V> matcher)
8489
converted = (Matcher) matcher;
8590
}
8691

87-
return originalType.createQuery(field, converted);
92+
return originalType.createQuery(encounter, field, converted);
8893
}
8994

9095
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package se.l4.silo.engine.index.search.internal;
2+
3+
import org.apache.lucene.search.BooleanClause;
4+
import org.apache.lucene.search.BooleanQuery;
5+
import org.apache.lucene.search.Query;
6+
import org.apache.lucene.util.QueryBuilder;
7+
import org.eclipse.collections.api.factory.Sets;
8+
import org.eclipse.collections.api.set.ImmutableSet;
9+
10+
import se.l4.silo.StorageException;
11+
import se.l4.silo.engine.index.search.locales.LocaleSupport;
12+
import se.l4.silo.engine.index.search.query.QueryEncounter;
13+
14+
public class UserQueryParser
15+
{
16+
private final QueryEncounter<?> encounter;
17+
private boolean typeAhead;
18+
private ImmutableSet<String> fields;
19+
20+
private UserQueryParser(
21+
QueryEncounter<?> encounter
22+
)
23+
{
24+
fields = Sets.immutable.empty();
25+
this.encounter = encounter;
26+
}
27+
28+
public static UserQueryParser create(
29+
QueryEncounter<?> encounter
30+
)
31+
{
32+
return new UserQueryParser(encounter);
33+
}
34+
35+
/**
36+
* Set the fields to match.
37+
*
38+
* @param fields
39+
* @return
40+
*/
41+
public UserQueryParser withFields(String... fields)
42+
{
43+
this.fields = Sets.immutable.of(fields);
44+
return this;
45+
}
46+
47+
/**
48+
* Set if this should be parsed as a type-ahead query.
49+
*
50+
* @param x
51+
* @return
52+
*/
53+
public UserQueryParser withTypeAhead(boolean typeAhead)
54+
{
55+
this.typeAhead = typeAhead;
56+
return this;
57+
}
58+
59+
public Query parse(
60+
String q
61+
)
62+
{
63+
return parse(encounter.currentLanguage(), q);
64+
}
65+
66+
private Query parse(LocaleSupport locale, String query)
67+
{
68+
QueryBuilder builder = new QueryBuilder(
69+
typeAhead ? locale.getTypeAheadAnalyzer() : locale.getTextAnalyzer()
70+
);
71+
72+
boolean inQuote = false;
73+
BooleanQuery.Builder result = new BooleanQuery.Builder();
74+
int last = 0;
75+
for(int i=0, n=query.length(); i<n; i++)
76+
{
77+
char c = query.charAt(i);
78+
if(c == '"')
79+
{
80+
if(inQuote)
81+
{
82+
Query fq = getPhraseQuery(
83+
builder,
84+
query.substring(last, i)
85+
);
86+
87+
if(fq != null)
88+
{
89+
result.add(fq, BooleanClause.Occur.MUST);
90+
}
91+
}
92+
else if(last < i-1)
93+
{
94+
Query fq = getBooleanQuery(
95+
builder,
96+
query.substring(last, i)
97+
);
98+
99+
if(fq != null)
100+
{
101+
result.add(fq, BooleanClause.Occur.MUST);
102+
}
103+
}
104+
105+
last = i+1;
106+
inQuote = ! inQuote;
107+
}
108+
}
109+
110+
if(inQuote)
111+
{
112+
Query fq = getPhraseQuery(
113+
builder,
114+
query.substring(last, query.length())
115+
);
116+
117+
if(fq != null)
118+
{
119+
result.add(fq, BooleanClause.Occur.MUST);
120+
}
121+
}
122+
else if(last < query.length())
123+
{
124+
Query fq = getBooleanQuery(
125+
builder,
126+
query.substring(last, query.length())
127+
);
128+
129+
if(fq != null)
130+
{
131+
result.add(fq, BooleanClause.Occur.MUST);
132+
}
133+
}
134+
135+
return result.build();
136+
}
137+
138+
private Query getBooleanQuery(
139+
QueryBuilder builder,
140+
String q
141+
)
142+
{
143+
if(fields.size() == 1)
144+
{
145+
return builder.createBooleanQuery(fields.getAny(), q, BooleanClause.Occur.MUST);
146+
}
147+
148+
throw new StorageException("No support for querying multiple fields");
149+
}
150+
151+
private Query getPhraseQuery(
152+
QueryBuilder builder,
153+
String q
154+
)
155+
{
156+
if(fields.size() == 1)
157+
{
158+
return builder.createPhraseQuery(fields.getAny(), q);
159+
}
160+
161+
throw new StorageException("No support for querying multiple fields");
162+
}
163+
}

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/types/BinaryFieldType.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import se.l4.exobytes.streaming.Token;
2222
import se.l4.silo.engine.index.search.SearchFieldDef;
2323
import se.l4.silo.engine.index.search.facets.FacetCollector;
24+
import se.l4.silo.engine.index.search.query.QueryEncounter;
2425
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
2526
import se.l4.silo.engine.index.search.types.SearchFieldType;
2627
import se.l4.silo.index.EqualsMatcher;
@@ -102,7 +103,11 @@ public void create(FieldCreationEncounter<byte[]> encounter)
102103
}
103104

104105
@Override
105-
public Query createQuery(String field, Matcher<byte[]> matcher)
106+
public Query createQuery(
107+
QueryEncounter<?> encounter,
108+
String field,
109+
Matcher<byte[]> matcher
110+
)
106111
{
107112
if(matcher instanceof EqualsMatcher)
108113
{

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/types/BooleanFieldType.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import se.l4.exobytes.streaming.Token;
2323
import se.l4.silo.engine.index.search.SearchFieldDef;
2424
import se.l4.silo.engine.index.search.facets.FacetCollector;
25+
import se.l4.silo.engine.index.search.query.QueryEncounter;
2526
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
2627
import se.l4.silo.engine.index.search.types.SearchFieldType;
2728
import se.l4.silo.index.EqualsMatcher;
@@ -112,7 +113,11 @@ public void create(FieldCreationEncounter<Boolean> encounter)
112113
}
113114

114115
@Override
115-
public Query createQuery(String field, Matcher<Boolean> matcher)
116+
public Query createQuery(
117+
QueryEncounter<?> encounter,
118+
String field,
119+
Matcher<Boolean> matcher
120+
)
116121
{
117122
if(matcher instanceof EqualsMatcher)
118123
{

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/types/DoubleFieldType.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import se.l4.exobytes.streaming.StreamingInput;
1313
import se.l4.exobytes.streaming.StreamingOutput;
1414
import se.l4.exobytes.streaming.Token;
15+
import se.l4.silo.engine.index.search.query.QueryEncounter;
1516
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
1617
import se.l4.silo.index.EqualsMatcher;
1718
import se.l4.silo.index.Matcher;
@@ -73,7 +74,11 @@ public SortField createSortField(String field, boolean ascending)
7374
}
7475

7576
@Override
76-
public Query createQuery(String field, Matcher<Double> matcher)
77+
public Query createQuery(
78+
QueryEncounter<?> encounter,
79+
String field,
80+
Matcher<Double> matcher
81+
)
7782
{
7883
if(matcher instanceof EqualsMatcher)
7984
{

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/types/FloatFieldType.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import se.l4.exobytes.streaming.StreamingInput;
1313
import se.l4.exobytes.streaming.StreamingOutput;
1414
import se.l4.exobytes.streaming.Token;
15+
import se.l4.silo.engine.index.search.query.QueryEncounter;
1516
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
1617
import se.l4.silo.index.EqualsMatcher;
1718
import se.l4.silo.index.Matcher;
@@ -73,7 +74,11 @@ public SortField createSortField(String field, boolean ascending)
7374
}
7475

7576
@Override
76-
public Query createQuery(String field, Matcher<Float> matcher)
77+
public Query createQuery(
78+
QueryEncounter<?> encounter,
79+
String field,
80+
Matcher<Float> matcher
81+
)
7782
{
7883
if(matcher instanceof EqualsMatcher)
7984
{

silo-search-engine/src/main/java/se/l4/silo/engine/index/search/internal/types/FullTextFieldType.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
import org.apache.lucene.search.Query;
66
import org.apache.lucene.search.TermQuery;
77

8+
import se.l4.silo.engine.index.search.internal.UserQueryParser;
9+
import se.l4.silo.engine.index.search.query.QueryEncounter;
810
import se.l4.silo.engine.index.search.types.AnalyzingTextField;
911
import se.l4.silo.engine.index.search.types.FieldCreationEncounter;
1012
import se.l4.silo.engine.index.search.types.StringFieldType;
1113
import se.l4.silo.index.EqualsMatcher;
1214
import se.l4.silo.index.Matcher;
1315
import se.l4.silo.index.search.SearchIndexException;
16+
import se.l4.silo.index.search.query.UserQuery;
17+
import se.l4.silo.index.search.query.UserQueryMatcher;
1418

1519
public class FullTextFieldType
1620
extends AbstractStringFieldType
@@ -53,13 +57,25 @@ protected void createIndexed(FieldCreationEncounter<String> encounter)
5357
}
5458

5559
@Override
56-
public Query createQuery(String field, Matcher<String> matcher)
60+
public Query createQuery(
61+
QueryEncounter<?> encounter,
62+
String field,
63+
Matcher<String> matcher
64+
)
5765
{
5866
if(matcher instanceof EqualsMatcher)
5967
{
6068
String value = ((EqualsMatcher<String>) matcher).getValue();
6169
return new TermQuery(new Term(field, value.toString()));
6270
}
71+
else if(matcher instanceof UserQueryMatcher)
72+
{
73+
UserQueryMatcher userQuery = (UserQueryMatcher) matcher;
74+
return UserQueryParser.create(encounter)
75+
.withFields(field)
76+
.withTypeAhead(userQuery.getContext() == UserQuery.Context.TYPE_AHEAD)
77+
.parse(userQuery.getQuery());
78+
}
6379

6480
throw new SearchIndexException("Token field queries require a " + EqualsMatcher.class.getName());
6581
}

0 commit comments

Comments
 (0)