Manybubbles has uploaded a new change for review.
https://gerrit.wikimedia.org/r/165480
Change subject: WIP: stuff
......................................................................
WIP: stuff
1. Javadoc
2. reject_unaccelerated to reject regexes that cannot be accelerated
3. test for changing gram size
NOCOMMIT: The tests for changing the gram size are a bit flaky right now-
they fail 1/3 of the time or so.
Change-Id: I5fa944ab285b410103cce0cb30ec53fbc0d5705e
---
M src/main/java/org/wikimedia/search/extra/ExtraPlugin.java
A src/main/java/org/wikimedia/search/extra/package-info.java
M src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilter.java
M src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterBuilder.java
M src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterParser.java
A
src/main/java/org/wikimedia/search/extra/regex/UnableToAccelerateRegexException.java
M
src/main/java/org/wikimedia/search/extra/regex/expression/AbstractCompositeExpression.java
M src/main/java/org/wikimedia/search/extra/regex/expression/Expression.java
M
src/main/java/org/wikimedia/search/extra/regex/expression/ExpressionSource.java
M src/main/java/org/wikimedia/search/extra/regex/expression/False.java
M src/main/java/org/wikimedia/search/extra/regex/expression/True.java
M
src/main/java/org/wikimedia/search/extra/regex/ngram/AutomatonTooComplexException.java
M src/main/java/org/wikimedia/search/extra/regex/ngram/NGramAutomaton.java
M src/main/java/org/wikimedia/search/extra/regex/ngram/NGramExtractor.java
M src/main/java/org/wikimedia/search/extra/util/FieldValues.java
M src/main/java/org/wikimedia/search/extra/util/Functions.java
A src/main/java/org/wikimedia/search/extra/util/package-info.java
M src/test/java/org/wikimedia/search/extra/regex/SourceRegexFilterTest.java
18 files changed, 312 insertions(+), 81 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/search/extra
refs/changes/80/165480/1
diff --git a/src/main/java/org/wikimedia/search/extra/ExtraPlugin.java
b/src/main/java/org/wikimedia/search/extra/ExtraPlugin.java
index 70f0c95..f0a5b92 100644
--- a/src/main/java/org/wikimedia/search/extra/ExtraPlugin.java
+++ b/src/main/java/org/wikimedia/search/extra/ExtraPlugin.java
@@ -4,6 +4,9 @@
import org.elasticsearch.plugins.AbstractPlugin;
import org.wikimedia.search.extra.regex.SourceRegexFilterParser;
+/**
+ * Setup the Elasticsearch plugin.
+ */
public class ExtraPlugin extends AbstractPlugin {
@Override
public String description() {
@@ -15,6 +18,9 @@
return "wikimedia-extra";
}
+ /**
+ * Register our parsers.
+ */
public void onModule(IndicesQueriesModule module) {
module.addFilter(new SourceRegexFilterParser());
}
diff --git a/src/main/java/org/wikimedia/search/extra/package-info.java
b/src/main/java/org/wikimedia/search/extra/package-info.java
new file mode 100644
index 0000000..910721c
--- /dev/null
+++ b/src/main/java/org/wikimedia/search/extra/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Utilities.
+ */
+package org.wikimedia.search.extra;
\ No newline at end of file
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilter.java
b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilter.java
index 6461ca0..6f36195 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilter.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilter.java
@@ -33,13 +33,14 @@
private final int maxInspect;
private final boolean caseSensitive;
private final Locale locale;
+ private final boolean rejectUnaccelerated;
private int inspected = 0;
private Filter prefilter;
private CharacterRunAutomaton charRun;
public SourceRegexFilter(String fieldPath, FieldValues.Loader loader,
String regex, String ngramFieldPath, int gramSize, int maxExpand,
- int maxStatesTraced, int maxInspect, boolean caseSensitive, Locale
locale) {
+ int maxStatesTraced, int maxInspect, boolean caseSensitive, Locale
locale, boolean rejectUnaccelerated) {
this.fieldPath = fieldPath;
this.loader = loader;
this.regex = regex;
@@ -50,6 +51,7 @@
this.maxInspect = maxInspect;
this.caseSensitive = caseSensitive;
this.locale = locale;
+ this.rejectUnaccelerated = rejectUnaccelerated;
}
@Override
@@ -73,6 +75,9 @@
Automaton automaton = new RegExp(regex.toLowerCase(locale),
RegExp.ALL ^ RegExp.AUTOMATON).toAutomaton();
Expression<String> expression = new NGramExtractor(gramSize,
maxExpand, maxStatesTraced).extract(automaton).simplify();
if (expression.alwaysTrue()) {
+ if (rejectUnaccelerated) {
+ throw new UnableToAccelerateRegexException(regex,
gramSize, ngramFieldPath);
+ }
prefilter = Queries.MATCH_ALL_FILTER;
} else if (expression.alwaysFalse()) {
prefilter = Queries.MATCH_NO_FILTER;
@@ -103,6 +108,7 @@
@Override
protected boolean match(int docid) {
if (inspected >= maxInspect) {
+ // TODO hook into the generic timeout mechanism when it is
ready
return false;
}
inspected++;
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterBuilder.java
b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterBuilder.java
index 14fee4d..356b361 100644
---
a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterBuilder.java
+++
b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterBuilder.java
@@ -6,6 +6,9 @@
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.BaseFilterBuilder;
+/**
+ * Builds source_regex filters.
+ */
public class SourceRegexFilterBuilder extends BaseFilterBuilder {
private final String field;
private final String regex;
@@ -17,22 +20,43 @@
private Integer maxInspect;
private Boolean caseSensitive;
private Locale locale;
+ private Boolean rejectUnaccelerated;
+ /**
+ * Start building.
+ * @param field the field to load and run the regex against
+ * @param regex the regex to run
+ */
public SourceRegexFilterBuilder(String field, String regex) {
this.field = field;
this.regex = regex;
}
+ /**
+ * @param loadFromSource should field be loaded from source (true) or from
a
+ * stored field (false)?
+ * @return this for chaining
+ */
public SourceRegexFilterBuilder loadFromSource(boolean loadFromSource) {
this.loadFromSource = loadFromSource;
return this;
}
+ /**
+ * @param ngramField field containing ngrams used to prefilter checked
+ * documents. If not set then no ngram acceleration is
performed.
+ * @return this for chaining
+ */
public SourceRegexFilterBuilder ngramField(String ngramField) {
this.ngramField = ngramField;
return this;
}
+ /**
+ * @param gramSize size of the gram. Defaults to 3 because everyone loves
+ * trigrams.
+ * @return this for chaining
+ */
public SourceRegexFilterBuilder gramSize(int gramSize) {
this.gramSize = gramSize;
return this;
@@ -79,6 +103,16 @@
return this;
}
+ /**
+ * @param rejectUnaccelerated should the filter reject regexes it cannot
+ * accelerate?
+ * @return this for chaining
+ */
+ public SourceRegexFilterBuilder rejectUnaccelerated(boolean
rejectUnaccelerated) {
+ this.rejectUnaccelerated = rejectUnaccelerated;
+ return this;
+ }
+
@Override
protected void doXContent(XContentBuilder builder, Params params) throws
IOException {
builder.startObject("source-regex");
@@ -109,6 +143,9 @@
if (locale != null) {
builder.field("locale", locale);
}
+ if (rejectUnaccelerated != null) {
+ builder.field("reject_unaccelerated", rejectUnaccelerated);
+ }
builder.endObject();
}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterParser.java
b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterParser.java
index 2c0f824..9c1c3e9 100644
---
a/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterParser.java
+++
b/src/main/java/org/wikimedia/search/extra/regex/SourceRegexFilterParser.java
@@ -11,6 +11,9 @@
import org.elasticsearch.index.query.QueryParsingException;
import org.wikimedia.search.extra.util.FieldValues;
+/**
+ * Parses source_regex filters.
+ */
public class SourceRegexFilterParser implements
org.elasticsearch.index.query.FilterParser {
private static final String[] NAMES = new String[] { "source_regex",
"source-regex", "sourceRegex" };
@@ -32,6 +35,7 @@
int maxInspect = Integer.MAX_VALUE;
boolean caseSensitive = false;
Locale locale = Locale.ROOT;
+ boolean rejectUnaccelerated = false;
// Stuff all filters have
String filterName = null;
@@ -69,6 +73,8 @@
caseSensitive = parser.booleanValue();
} else if ("locale".equals(currentFieldName)) {
locale = LocaleUtils.parse(parser.text());
+ } else if ("reject_unaccelerated".equals(currentFieldName) ||
"rejectUnaccelerated".equals(currentFieldName)) {
+ rejectUnaccelerated = parser.booleanValue();
} else if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
} else if ("_name".equals(currentFieldName)) {
@@ -82,14 +88,14 @@
}
}
- if (regex == null) {
+ if (regex == null || "".equals(regex)) {
throw new QueryParsingException(parseContext.index(),
"[source-regex] filter must specify [regex]");
}
if (fieldPath == null) {
throw new QueryParsingException(parseContext.index(),
"[source-regex] filter must specify [field]");
}
Filter filter = new SourceRegexFilter(fieldPath, loader, regex,
ngramFieldPath, gramSize, maxExpand, maxStatesTraced, maxInspect,
- caseSensitive, locale);
+ caseSensitive, locale, rejectUnaccelerated);
if (cache) {
filter = parseContext.cacheFilter(filter, cacheKey);
}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/UnableToAccelerateRegexException.java
b/src/main/java/org/wikimedia/search/extra/regex/UnableToAccelerateRegexException.java
new file mode 100644
index 0000000..4c96d3e
--- /dev/null
+++
b/src/main/java/org/wikimedia/search/extra/regex/UnableToAccelerateRegexException.java
@@ -0,0 +1,15 @@
+package org.wikimedia.search.extra.regex;
+
+import java.util.Locale;
+
+/**
+ * Thrown when the filter is unable to accelerate a regex and
+ * rejectUnaccelerated is set.
+ */
+public class UnableToAccelerateRegexException extends RuntimeException {
+ private static final long serialVersionUID = 2685216158813374775L;
+
+ public UnableToAccelerateRegexException(String regex, int gramSize, String
ngramField) {
+ super(String.format(Locale.ROOT, "Unable to accelerate %s with %s
sized grams stored in %s", regex, gramSize, ngramField));
+ }
+}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/expression/AbstractCompositeExpression.java
b/src/main/java/org/wikimedia/search/extra/regex/expression/AbstractCompositeExpression.java
index 5679047..33356e1 100644
---
a/src/main/java/org/wikimedia/search/extra/regex/expression/AbstractCompositeExpression.java
+++
b/src/main/java/org/wikimedia/search/extra/regex/expression/AbstractCompositeExpression.java
@@ -20,6 +20,11 @@
this.components = components;
}
+ /**
+ * Transform the components of this composite expression. Used by
subclasses when implementing transform.
+ * @param transformer transformer to use
+ * @return result of transforming components
+ */
protected <J> ImmutableSet<J>
transformComponents(Expression.Transformer<T, J> transformer) {
ImmutableSet.Builder<J> builder = ImmutableSet.builder();
for (Expression<T> component : components) {
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/expression/Expression.java
b/src/main/java/org/wikimedia/search/extra/regex/expression/Expression.java
index 57d91f6..f123201 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/expression/Expression.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/expression/Expression.java
@@ -7,6 +7,8 @@
* transformed. Simplifying expressions eliminates extraneous terms and factors
* out common terms. Transformation allows client code to convert the
expression
* to some other (maybe evaluable) form.
+ *
+ * @param <T> type stored in leaves
*/
public interface Expression<T> {
/**
@@ -38,14 +40,51 @@
/**
* Transform this expression into another form.
+ *
+ * @param <T> type stored in leaves
+ * @param <J> result of the transformation.
*/
<J> J transform(Transformer<T, J> transformer);
+ /**
+ * Transformer for expression components.
+ *
+ * @param <T> type stored in leaves
+ * @param <J> result of the transformation.
+ */
interface Transformer<T, J> {
+ /**
+ * Transform an expression that is always true.
+ */
J alwaysTrue();
+
+ /**
+ * Transform an expression that is always false.
+ */
J alwaysFalse();
+
+ /**
+ * Transform a leaf expression.
+ *
+ * @param t data stored in the leaf
+ * @return result of the transform
+ */
J leaf(T t);
+
+ /**
+ * Transform an and expression.
+ *
+ * @param js transformed sub-expressions
+ * @return result of the transform
+ */
J and(ImmutableSet<J> js);
+
+ /**
+ * Transform an or expression.
+ *
+ * @param js transformed sub-expressions
+ * @return result of the transform
+ */
J or(ImmutableSet<J> js);
}
}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/expression/ExpressionSource.java
b/src/main/java/org/wikimedia/search/extra/regex/expression/ExpressionSource.java
index fd0b9e1..9e824c5 100644
---
a/src/main/java/org/wikimedia/search/extra/regex/expression/ExpressionSource.java
+++
b/src/main/java/org/wikimedia/search/extra/regex/expression/ExpressionSource.java
@@ -1,5 +1,14 @@
package org.wikimedia.search.extra.regex.expression;
+/**
+ * Type that can be expressed as an expression.
+ *
+ * @param <T> type stored in leaves
+ */
public interface ExpressionSource<T> {
+ /**
+ * This expressed as an expression. The result might not be simplified so
+ * call simplify on it if you need it simplified.
+ */
Expression<T> expression();
}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/expression/False.java
b/src/main/java/org/wikimedia/search/extra/regex/expression/False.java
index c81d0a5..b5fcd75 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/expression/False.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/expression/False.java
@@ -6,6 +6,9 @@
public class False<T> implements Expression<T> {
private static final False<Object> TRUE = new False<>();
+ /**
+ * There is only one false.
+ */
@SuppressWarnings("unchecked")
public static <T> False<T> instance() {
return (False<T>) TRUE;
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/expression/True.java
b/src/main/java/org/wikimedia/search/extra/regex/expression/True.java
index 734084a..3a38d53 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/expression/True.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/expression/True.java
@@ -6,6 +6,9 @@
public class True<T> implements Expression<T> {
private static final True<Object> TRUE = new True<>();
+ /**
+ * There is only one True.
+ */
@SuppressWarnings("unchecked")
public static <T> True<T> instance() {
return (True<T>) TRUE;
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/ngram/AutomatonTooComplexException.java
b/src/main/java/org/wikimedia/search/extra/regex/ngram/AutomatonTooComplexException.java
index e0f2960..38cc9f0 100644
---
a/src/main/java/org/wikimedia/search/extra/regex/ngram/AutomatonTooComplexException.java
+++
b/src/main/java/org/wikimedia/search/extra/regex/ngram/AutomatonTooComplexException.java
@@ -1,8 +1,15 @@
package org.wikimedia.search.extra.regex.ngram;
+/**
+ * Thrown when the automaton is too complex to convert to ngrams (as measured
by
+ * maxExpand).
+ */
public class AutomatonTooComplexException extends IllegalArgumentException {
private static final long serialVersionUID = -4686819368713525883L;
+ /**
+ * Build it.
+ */
public AutomatonTooComplexException() {
super("The supplied automaton is too complex to extract ngrams");
}
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramAutomaton.java
b/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramAutomaton.java
index 4e925f5..5b7d30f 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramAutomaton.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramAutomaton.java
@@ -33,6 +33,18 @@
private final List<NGramState> acceptStates = new ArrayList<>();
private final Map<NGramState, NGramState> states = new HashMap<>();
+ /**
+ * Build it.
+ * @param source automaton to convert into an ngram automaton
+ * @param gramSize size of the grams to extract
+ * @param maxExpand Maximum size of range transitions to expand into single
+ * transitions. Its roughly analogous to the number of character
+ * in a character class before it is considered a wildcard for
+ * optimization purposes.
+ * @param maxStatesTraced maximum number of states traced during automaton
+ * functions. Higher number allow more complex automata to be
+ * converted to ngram expressions at the cost of more time.
+ */
public NGramAutomaton(Automaton source, int gramSize, int maxExpand, int
maxStatesTraced) {
this.source = source.getNumberedStates();
this.gramSize = gramSize;
diff --git
a/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramExtractor.java
b/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramExtractor.java
index 0c6f7e0..d4ff675 100644
--- a/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramExtractor.java
+++ b/src/main/java/org/wikimedia/search/extra/regex/ngram/NGramExtractor.java
@@ -7,17 +7,35 @@
import org.wikimedia.search.extra.regex.expression.Leaf;
import org.wikimedia.search.extra.regex.expression.True;
+/**
+ * Extracts ngrams from automatons.
+ */
public class NGramExtractor {
private final int gramSize;
private final int maxExpand;
private final int maxStatesTraced;
+ /**
+ * Build it.
+ *
+ * @param gramSize size of the ngram. The "n" in ngram.
+ * @param maxExpand Maximum size of range transitions to expand into single
+ * transitions. Its roughly analogous to the number of character
+ * in a character class before it is considered a wildcard for
+ * optimization purposes.
+ * @param maxStatesTraced maximum number of states traced during automaton
+ * functions. Higher number allow more complex automata to be
+ * converted to ngram expressions at the cost of more time.
+ */
public NGramExtractor(int gramSize, int maxExpand, int maxStatesTraced) {
this.gramSize = gramSize;
this.maxExpand = maxExpand;
this.maxStatesTraced = maxStatesTraced;
}
+ /**
+ * Extract an Expression containing ngrams from an automaton.
+ */
public Expression<String> extract(Automaton automaton) {
if (automaton.getSingleton() != null) {
int end = automaton.getSingleton().length() - gramSize + 1;
diff --git a/src/main/java/org/wikimedia/search/extra/util/FieldValues.java
b/src/main/java/org/wikimedia/search/extra/util/FieldValues.java
index 7158a54..dc16f68 100644
--- a/src/main/java/org/wikimedia/search/extra/util/FieldValues.java
+++ b/src/main/java/org/wikimedia/search/extra/util/FieldValues.java
@@ -14,11 +14,17 @@
import org.elasticsearch.index.fieldvisitor.CustomFieldsVisitor;
import org.elasticsearch.index.fieldvisitor.JustSourceFieldsVisitor;
+/**
+ * Hub for fetching field values.
+ */
public abstract class FieldValues {
/**
* Loads field values.
*/
public interface Loader {
+ /**
+ * Load the value of the string at path from reader for docId.
+ */
List<String> load(String path, IndexReader reader, int docId) throws
IOException;
}
diff --git a/src/main/java/org/wikimedia/search/extra/util/Functions.java
b/src/main/java/org/wikimedia/search/extra/util/Functions.java
index 9f6ea87..2790df3 100644
--- a/src/main/java/org/wikimedia/search/extra/util/Functions.java
+++ b/src/main/java/org/wikimedia/search/extra/util/Functions.java
@@ -6,6 +6,10 @@
* Some useful functions.
*/
public class Functions {
+ /**
+ * Call toString on the provided object. This normally comes with Guava but
+ * Elasticsearch shades it and that somehow makes it invisible.
+ */
public static Function<Object, String> toStringFunction() {
return TO_STRING;
}
@@ -14,10 +18,6 @@
// Utils class
}
- /**
- * Call toString on the provided object. This normally comes with Guava but
- * Elasticsearch shades it and that somehow makes it invisible.
- */
private static final Function<Object, String> TO_STRING = new
Function<Object, String>() {
@Override
public String apply(Object obj) {
diff --git a/src/main/java/org/wikimedia/search/extra/util/package-info.java
b/src/main/java/org/wikimedia/search/extra/util/package-info.java
new file mode 100644
index 0000000..bbf237a
--- /dev/null
+++ b/src/main/java/org/wikimedia/search/extra/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Extracts ngrams from an {@link org.apache.lucene.util.automaton.Automaton}.
+ */
+package org.wikimedia.search.extra.util;
\ No newline at end of file
diff --git
a/src/test/java/org/wikimedia/search/extra/regex/SourceRegexFilterTest.java
b/src/test/java/org/wikimedia/search/extra/regex/SourceRegexFilterTest.java
index 4108342..4c6a309 100644
--- a/src/test/java/org/wikimedia/search/extra/regex/SourceRegexFilterTest.java
+++ b/src/test/java/org/wikimedia/search/extra/regex/SourceRegexFilterTest.java
@@ -28,13 +28,12 @@
@ElasticsearchIntegrationTest.ClusterScope(scope =
ElasticsearchIntegrationTest.Scope.SUITE, transportClientRatio = 0.0)
public class SourceRegexFilterTest extends ElasticsearchIntegrationTest {
@Test
- public void basic() throws InterruptedException, ExecutionException,
IOException {
+ public void basicUnacceleratedRegex() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(false, doc("findme", "test"));
indexChaff(between(0, 10000));
- // Result is found by regex without acceleration
- SearchResponse response = search("t..t").get();
+ SearchResponse response = search(filter("t..t")).get();
assertSearchHits(response, "findme");
client().prepareDelete("test", "test", "findme").get();
@@ -42,68 +41,60 @@
refresh();
// Result isn't found when it is deleted
- response = search("t..t").get();
+ response = search(filter("t..t")).get();
assertHitCount(response, 0);
}
- /**
- * Regex can match the whole string.
- */
@Test
- public void wholeString() throws InterruptedException, ExecutionException,
IOException {
+ public void regexMatchesWholeString() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "test"));
- SearchResponse response = search("test").get();
+ SearchResponse response = search(filter("test")).get();
assertSearchHits(response, "findme");
}
- /**
- * Regex can match a word in the string.
- */
@Test
- public void instring() throws InterruptedException, ExecutionException,
IOException {
+ public void regexMatchesPartOfString() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "I have the test in me."));
- SearchResponse response = search("test").get();
+ SearchResponse response = search(filter("test")).get();
assertSearchHits(response, "findme");
}
- /**
- * Regex can match unicode characters.
- */
@Test
- public void unicode() throws InterruptedException, ExecutionException,
IOException {
+ public void regexMatchesUnicodeCharacters() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "solved using only λ+μ function"));
- SearchResponse response = search("only λ\\+μ").get();
+ SearchResponse response = search(filter("only λ\\+μ")).get();
+ assertSearchHits(response, "findme");
+
+ // It even works with ngram extraction!
+ response = search(filter("on[ly]y λ\\+μ")).get();
assertSearchHits(response, "findme");
}
- /**
- * maxStatesTraced limits the complexity of the regexes.
- */
@Test
- public void maxStatesTraced() throws InterruptedException,
ExecutionException, IOException {
+ public void maxStatesTracedLimitsComplexityOfRegexes() throws
InterruptedException, ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "test"));
- SearchResponse response = search(new SourceRegexFilterBuilder("test",
"te[st]t").ngramField("test.trigrams").maxStatesTraced(10)).get();
+ SearchResponse response =
search(filter("te[st]t").maxStatesTraced(10)).get();
assertHitCount(response, 1);
- response = search(new SourceRegexFilterBuilder("test",
"test").ngramField("test.trigrams").maxStatesTraced(0)).get();
+ // maxStatesTraced isn't used when the regex is just a sequence of
+ // characters
+ response = search(filter("test").maxStatesTraced(0)).get();
assertHitCount(response, 1);
+ // But it is used when there are any complex things in it
+ assertFailures(search(filter("te[st]t").maxStatesTraced(0)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("complex"));
// Its unfortunate that this comes back as an INTERNAL_SERVER_ERROR but
// I can't find any way from here to mark it otherwise.
- assertFailures(search(new SourceRegexFilterBuilder("test",
"te[st]t").ngramField("test.trigrams").maxStatesTraced(0)),
- RestStatus.INTERNAL_SERVER_ERROR, containsString("complex"));
}
- /**
- * maxInspect limits the number of matches.
- */
@Test
- public void maxInspect() throws InterruptedException, ExecutionException,
IOException {
+ public void maxInspectLimitsNumberOfMatches() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "test"));
- SearchResponse response = search(new SourceRegexFilterBuilder("test",
"test").maxInspect(0)).get();
+ SearchResponse response = search(filter("test").maxInspect(0)).get();
assertHitCount(response, 0);
List<IndexRequestBuilder> builders = new ArrayList<>();
@@ -111,45 +102,91 @@
builders.add(doc("findme" + i, "test"));
}
indexRandom(true, builders);
- response = search(new SourceRegexFilterBuilder("test",
"test").maxInspect(10)).get();
+ response = search(filter("test").maxInspect(10)).get();
assertHitCount(response, 10);
}
- /**
- * Regex can can insensitively.
- */
@Test
- public void caseInsensitive() throws InterruptedException,
ExecutionException, IOException {
+ public void rejectEmptyRegex() throws InterruptedException,
ExecutionException,
+ IOException {
+ setup();
+ // Its unfortunate that this comes back as an INTERNAL_SERVER_ERROR but
+ // I can't find any way from here to mark it otherwise.
+ assertFailures(search(filter("")),
+ RestStatus.BAD_REQUEST, containsString("filter must specify
[regex]"));
+ }
+
+ @Test
+ public void
rejectUnacceleratedCausesFailuresWhenItCannotAccelerateTheRegex() throws
InterruptedException, ExecutionException,
+ IOException {
+ setup();
+ indexRandom(true, doc("findme", "test"));
+ // Its unfortunate that this comes back as an INTERNAL_SERVER_ERROR but
+ // I can't find any way from here to mark it otherwise.
+
+ assertFailures(search(filter("...").rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ assertFailures(search(filter("t.p").rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ assertFailures(search(filter(".+pa").rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ assertFailures(search(filter("p").rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ }
+
+ @Test
+ public void caseInsensitiveMatching() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "I have the test in me."));
- SearchResponse response = search("i h[ai]ve").get();
+ SearchResponse response = search(filter("i h[ai]ve")).get();
assertSearchHits(response, "findme");
- response = search("I h[ai]ve").get();
+ response = search(filter("I h[ai]ve")).get();
assertSearchHits(response, "findme");
}
- /**
- * Regex can match case sensitively.
- */
@Test
- public void caseSensitive() throws InterruptedException,
ExecutionException, IOException {
+ public void caseSensitiveMatching() throws InterruptedException,
ExecutionException, IOException {
setup();
indexRandom(true, doc("findme", "I have the test in me."));
- SearchResponse response = search(new SourceRegexFilterBuilder("test",
"i h[ai]ve").ngramField("test.trigrams").caseSensitive(true)).get();
+ SearchResponse response = search(filter("i
h[ai]ve").caseSensitive(true)).get();
assertHitCount(response, 0);
- response = search(new SourceRegexFilterBuilder("test", "I
h[ai]ve").ngramField("test.trigrams").caseSensitive(true)).get();
+ response = search(filter("I h[ai]ve").caseSensitive(true)).get();
assertSearchHits(response, "findme");
}
- /**
- * More complex regexes work.
- */
@Test
public void complex() throws InterruptedException, ExecutionException,
IOException {
setup();
indexRandom(true, doc("findme", "I have the test in me."));
- SearchResponse response = search("h[efas] te.*me").get();
+ SearchResponse response = search(filter("h[efas] te.*me")).get();
assertSearchHits(response, "findme");
+ }
+
+ @Test
+ public void changingGramSize() throws InterruptedException,
ExecutionException, IOException {
+ setup();
+ indexRandom(true, doc("findme", "I have the test in me."));
+
+ // You can change the gram size to allow more degenerate regexes!
+ SearchResponse response =
search(filter("te.*me").gramSize(2).ngramField("test.bigram").rejectUnaccelerated(true)).get();
+ assertSearchHits(response, "findme");
+
+ // Proof the regex would fail without the new gram size:
+ assertFailures(search(filter("te.*me").rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ // Its unfortunate that this comes back as an INTERNAL_SERVER_ERROR but
+ // I can't find any way from here to mark it otherwise.
+
+ // You can also raise the gram size
+ response =
search(filter("test.*me").gramSize(4).ngramField("test.quadgram").rejectUnaccelerated(true)).get();
+ assertSearchHits(response, "findme");
+
+ // But that limits the regexes you can accelerate to those from which
+ // appropriate grams can be extracted
+
assertFailures(search(filter("tes.*me").gramSize(4).rejectUnaccelerated(true)),
+ RestStatus.INTERNAL_SERVER_ERROR, containsString("Unable to
accelerate"));
+ // Its unfortunate that this comes back as an INTERNAL_SERVER_ERROR but
+ // I can't find any way from here to mark it otherwise.
}
/**
@@ -185,10 +222,10 @@
start = System.currentTimeMillis();
for (int i = 0; i < rounds; i++) {
- SearchResponse response = search(regex).get();
+ SearchResponse response = search(filter(regex)).get();
assertSearchHits(response, "findme");
}
- logger.info("Accel: {}", (System.currentTimeMillis() - start) /
rounds);
+ logger.info("Accelerated: {}", (System.currentTimeMillis() - start) /
rounds);
}
private IndexRequestBuilder doc(String id, String fieldValue) {
@@ -209,10 +246,10 @@
}
}
- private SearchRequestBuilder search(String regex) {
+ private SourceRegexFilterBuilder filter(String regex) {
SourceRegexFilterBuilder builder = new
SourceRegexFilterBuilder("test", regex);
- builder.ngramField("test.trigrams");
- return search(builder);
+ builder.ngramField("test.trigram");
+ return builder;
}
private SearchRequestBuilder search(SourceRegexFilterBuilder builder) {
@@ -224,34 +261,25 @@
mapping.startObject("test").startObject("properties");
mapping.startObject("test");
mapping.field("type", "string");
- {
- mapping.startObject("fields").startObject("trigrams");
- mapping.field("type", "string");
- mapping.field("analyzer", "trigram");
- mapping.endObject().endObject();
- }
+ mapping.startObject("fields");
+ buildSubfield(mapping, "bigram");
+ buildSubfield(mapping, "trigram");
+ buildSubfield(mapping, "quadgram");
+ mapping.endObject();
mapping.endObject();
XContentBuilder settings =
jsonBuilder().startObject().startObject("index");
settings.field("number_of_shards", 1);
settings.startObject("analysis");
settings.startObject("analyzer");
- {
- settings.startObject("trigram");
- settings.field("type", "custom");
- settings.field("tokenizer", "trigram");
- settings.field("filter", new String[] {"lowercase"});
- settings.endObject();
- }
+ buildNgramAnalyzer(settings, "bigram");
+ buildNgramAnalyzer(settings, "trigram");
+ buildNgramAnalyzer(settings, "quadgram");
settings.endObject();
settings.startObject("tokenizer");
- {
- settings.startObject("trigram");
- settings.field("type", "nGram");
- settings.field("min_gram", "3");
- settings.field("max_gram", "3");
- settings.endObject();
- }
+ buildNgramTokenizer(settings, "bigram", 2);
+ buildNgramTokenizer(settings, "trigram", 3);
+ buildNgramTokenizer(settings, "quadgram", 4);
settings.endObject();
settings.endObject().endObject();
// System.err.println(settings.string());
@@ -260,6 +288,29 @@
ensureYellow();
}
+ private void buildSubfield(XContentBuilder mapping, String name) throws
IOException {
+ mapping.startObject(name);
+ mapping.field("type", "string");
+ mapping.field("analyzer", name);
+ mapping.endObject();
+ }
+
+ private void buildNgramAnalyzer(XContentBuilder settings, String name)
throws IOException {
+ settings.startObject(name);
+ settings.field("type", "custom");
+ settings.field("tokenizer", name);
+ settings.field("filter", new String[] {"lowercase"});
+ settings.endObject();
+ }
+
+ private void buildNgramTokenizer(XContentBuilder settings, String name,
int size) throws IOException {
+ settings.startObject(name);
+ settings.field("type", "nGram");
+ settings.field("min_gram", size);
+ settings.field("max_gram", size);
+ settings.endObject();
+ }
+
/**
* Enable plugin loading.
*/
--
To view, visit https://gerrit.wikimedia.org/r/165480
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I5fa944ab285b410103cce0cb30ec53fbc0d5705e
Gerrit-PatchSet: 1
Gerrit-Project: search/extra
Gerrit-Branch: master
Gerrit-Owner: Manybubbles <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits