#594 - ElasticSearch - Add support for TextSimple, TextQueryString and TextCommon full text search expressions

This commit is contained in:
Robin Bygrave
2016-03-11 14:52:27 +13:00
parent 75a84af3f8
commit 049bf76cc4
17 changed files with 994 additions and 48 deletions
@@ -2,6 +2,9 @@ package com.avaje.ebean;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import java.util.Collection;
import java.util.List;
@@ -298,6 +301,21 @@ public interface ExpressionFactory {
*/
Expression textMultiMatch(String query, MultiMatch options);
/**
* Create a text simple query expression (currently doc store/Elastic only).
*/
Expression textSimple(String search, TextSimple options);
/**
* Create a text query string expression (currently doc store/Elastic only).
*/
Expression textQueryString(String search, TextQueryString options);
/**
* Create a text common terms expression (currently doc store/Elastic only).
*/
Expression textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -2,6 +2,9 @@ package com.avaje.ebean;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
/**
* An list of Full text query expressions.
@@ -37,6 +40,21 @@ public interface TextExpressionList<T> extends ExpressionList<T> {
*/
TextExpressionList<T> multiMatch(String search, MultiMatch options);
/**
* Add a simple query string expression.
*/
TextExpressionList<T> textSimple(String search, TextSimple options);
/**
* Add a query string expression.
*/
TextExpressionList<T> textQueryString(String search, TextQueryString options);
/**
* Add common terms expression.
*/
TextExpressionList<T> textCommonTerms(String search, TextCommonTerms options);
/**
* Start a list of expressions that will be joined by MUST.
*/
@@ -5,7 +5,7 @@ package com.avaje.ebean.search;
*/
public abstract class AbstractMatch {
protected boolean and;
protected boolean operatorAnd;
protected String analyzer;
@@ -28,8 +28,8 @@ public abstract class AbstractMatch {
/**
* Return true if using the AND operator otherwise using the OR operator.
*/
public boolean isAnd() {
return and;
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
@@ -9,18 +9,7 @@ public class Match extends AbstractMatch {
protected boolean phrasePrefix;
/**
* Create and return Match options using AND operator.
*/
public static Match AND() {
return new Match().opAnd();
}
/**
* Create and return Match options using OR operator.
*/
public static Match OR() {
return new Match().opOr();
public Match() {
}
/**
@@ -43,7 +32,7 @@ public class Match extends AbstractMatch {
* Use the AND operator (rather than OR).
*/
public Match opAnd() {
and = true;
operatorAnd = true;
return this;
}
@@ -51,7 +40,7 @@ public class Match extends AbstractMatch {
* Use the OR operator (rather than AND).
*/
public Match opOr() {
and = false;
operatorAnd = false;
return this;
}
@@ -16,26 +16,19 @@ public class MultiMatch extends AbstractMatch {
PHRASE_PREFIX
}
protected Type type = Type.BEST_FIELDS;
protected final String[] fields;
protected String[] fields;
protected Type type = Type.BEST_FIELDS;
protected double tieBreaker;
/**
* Create and return Match options using AND operator.
* Create with the given fields.
*/
public static MultiMatch fields(String... fields) {
return new MultiMatch(fields);
}
/**
* Create and return Match options using OR operator.
*/
public static MultiMatch OR() {
return new MultiMatch().opOr();
}
/**
* Construct with a set of fields.
*/
@@ -63,7 +56,7 @@ public class MultiMatch extends AbstractMatch {
* Use the AND operator (rather than OR).
*/
public MultiMatch opAnd() {
and = true;
operatorAnd = true;
return this;
}
@@ -71,7 +64,7 @@ public class MultiMatch extends AbstractMatch {
* Use the OR operator (rather than AND).
*/
public MultiMatch opOr() {
and = false;
operatorAnd = false;
return this;
}
@@ -0,0 +1,141 @@
package com.avaje.ebean.search;
/**
* Text common terms query.
* <p>
* This maps to an ElasticSearch "common terms query".
* </p>
* <pre>{@code
*
* TextCommonTerms options = new TextCommonTerms()
* .cutoffFrequency(0.001)
* .minShouldMatch("50%")
* .lowFreqOperatorAnd(true)
* .highFreqOperatorAnd(true);
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textCommonTerms("the brown", options)
* .findList();
*
* }</pre>
*
* <pre>{@code
*
* // ElasticSearch expression
*
* "common": {
* "body": {
* "query": "the brown",
* "cutoff_frequency": 0.001,
* "low_freq_operator": "and",
* "high_freq_operator": "and",
* "minimum_should_match": "50%"
* }
* }
*
* }</pre>
*/
public class TextCommonTerms {
protected double cutoffFrequency;
protected boolean lowFreqOperatorAnd;
protected boolean highFreqOperatorAnd;
protected String minShouldMatch;
protected String minShouldMatchLowFreq;
protected String minShouldMatchHighFreq;
/**
* Set the cutoff frequency.
*/
public TextCommonTerms cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set to true if low frequency terms should use AND operator.
*/
public TextCommonTerms lowFreqOperatorAnd(boolean opAnd) {
this.lowFreqOperatorAnd = opAnd;
return this;
}
/**
* Set to true if high frequency terms should use AND operator.
*/
public TextCommonTerms highFreqOperatorAnd(boolean opAnd) {
this.highFreqOperatorAnd = opAnd;
return this;
}
/**
* Set the minimum should match.
*/
public TextCommonTerms minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the minimum should match for low frequency terms.
*/
public TextCommonTerms minShouldMatchLowFreq(String minShouldMatchLowFreq) {
this.minShouldMatchLowFreq = minShouldMatchLowFreq;
return this;
}
/**
* Set the minimum should match for high frequency terms.
*/
public TextCommonTerms minShouldMatchHighFreq(String minShouldMatchHighFreq) {
this.minShouldMatchHighFreq = minShouldMatchHighFreq;
return this;
}
/**
* Return true if low freq should use the AND operator.
*/
public boolean isLowFreqOperatorAnd() {
return lowFreqOperatorAnd;
}
/**
* Return true if high freq should use the AND operator.
*/
public boolean isHighFreqOperatorAnd() {
return highFreqOperatorAnd;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the minimum to match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the minimum to match for high frequency.
*/
public String getMinShouldMatchHighFreq() {
return minShouldMatchHighFreq;
}
/**
* Return the minimum to match for low frequency.
*/
public String getMinShouldMatchLowFreq() {
return minShouldMatchLowFreq;
}
}
@@ -0,0 +1,436 @@
package com.avaje.ebean.search;
/**
* Text query string options.
* <p>
* This maps to an ElasticSearch "query string query".
* </p>
*
* <pre>{@code
*
* TextQueryString options = new TextQueryString()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*
*
* <pre>{@code
*
* // just use default options
* TextQueryString options = new TextQueryString();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*
*/
public class TextQueryString {
public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = 50;
public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
protected final String[] fields;
/**
* Only used when multiple fields set.
*/
protected boolean useDisMax = true;
/**
* Only used when multiple fields set.
*/
protected double tieBreaker;
protected String defaultField;
protected boolean operatorAnd;
protected String analyzer;
protected boolean allowLeadingWildcard = true;
protected boolean lowercaseExpandedTerms = true;
protected boolean enablePositionIncrements = true;
protected int fuzzyMaxExpansions = DEFAULT_FUZZY_MAX_EXPANSIONS;
protected String fuzziness;
protected int fuzzyPrefixLength;
protected double phraseSlop;
protected double boost;
protected boolean analyzeWildcard;
protected boolean autoGeneratePhraseQueries;
protected int maxDeterminizedStates = DEFAULT_MAX_DETERMINIZED_STATES;
protected String minShouldMatch;
protected boolean lenient;
protected String locale;
protected String timeZone;
protected String rewrite;
/**
* Create with given fields.
*/
public static TextQueryString fields(String... fields) {
return new TextQueryString(fields);
}
/**
* Construct with the fields to use.
*/
public TextQueryString(String... fields) {
this.fields = fields;
}
/**
* Use the AND operator (rather than OR).
*/
public TextQueryString opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public TextQueryString opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the locale.
*/
public TextQueryString locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set lenient mode.
*/
public TextQueryString lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextQueryString minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the analyzer.
*/
public TextQueryString analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set useDisMax option (when multiple fields only).
*/
public TextQueryString useDisMax(boolean useDisMax) {
this.useDisMax = useDisMax;
return this;
}
/**
* Set tieBreaker option (when multiple fields only).
*/
public TextQueryString tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Set the default field.
*/
public TextQueryString defaultField(String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* Set allow leading wildcard mode.
*/
public TextQueryString allowLeadingWildcard(boolean allowLeadingWildcard) {
this.allowLeadingWildcard = allowLeadingWildcard;
return this;
}
/**
* Set lowercase expanded terms mode.
*/
public TextQueryString lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set enable position increments mode.
*/
public TextQueryString enablePositionIncrements(boolean enablePositionIncrements) {
this.enablePositionIncrements = enablePositionIncrements;
return this;
}
/**
* Set fuzzy max expansions.
*/
public TextQueryString fuzzyMaxExpansions(int fuzzyMaxExpansions) {
this.fuzzyMaxExpansions = fuzzyMaxExpansions;
return this;
}
/**
* Set fuzziness.
*/
public TextQueryString fuzziness(String fuzziness) {
this.fuzziness = fuzziness;
return this;
}
/**
* Set the fuzzy prefix length.
*/
public TextQueryString fuzzyPrefixLength(int fuzzyPrefixLength) {
this.fuzzyPrefixLength = fuzzyPrefixLength;
return this;
}
/**
* Set the phrase slop.
*/
public TextQueryString phraseSlop(double phraseSlop) {
this.phraseSlop = phraseSlop;
return this;
}
/**
* Set the boost.
*/
public TextQueryString boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the analyze wildcard mode.
*/
public TextQueryString analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the auto generate phrase queries mode.
*/
public TextQueryString autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
return this;
}
/**
* Set the max determinized states.
*/
public TextQueryString maxDeterminizedStates(int maxDeterminizedStates) {
this.maxDeterminizedStates = maxDeterminizedStates;
return this;
}
/**
* Set the time zone.
*/
public TextQueryString timeZone(String timeZone) {
this.timeZone = timeZone;
return this;
}
/**
* Set the rewrite option.
*/
public TextQueryString rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return true if AND is the default operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the useDixMax mode.
*/
public boolean isUseDisMax() {
return useDisMax;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
/**
* Return the default field.
*/
public String getDefaultField() {
return defaultField;
}
/**
* Return the allow leading wildcard mode.
*/
public boolean isAllowLeadingWildcard() {
return allowLeadingWildcard;
}
/**
* Return the lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return the enable position increments mode.
*/
public boolean isEnablePositionIncrements() {
return enablePositionIncrements;
}
/**
* Return the fuzzy max expansions.
*/
public int getFuzzyMaxExpansions() {
return fuzzyMaxExpansions;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the fuzzy prefix length.
*/
public int getFuzzyPrefixLength() {
return fuzzyPrefixLength;
}
/**
* Return the phrase slop.
*/
public double getPhraseSlop() {
return phraseSlop;
}
/**
* Return the analyze wildcard mode.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the auto generate phase queries mode.
*/
public boolean isAutoGeneratePhraseQueries() {
return autoGeneratePhraseQueries;
}
/**
* Return the max determinized states.
*/
public int getMaxDeterminizedStates() {
return maxDeterminizedStates;
}
/**
* Return the time zone.
*/
public String getTimeZone() {
return timeZone;
}
}
@@ -0,0 +1,194 @@
package com.avaje.ebean.search;
/**
* Simple text query options.
* <p>
* This maps to an ElasticSearch "simple text query".
* </p>
*
* <pre>{@code
*
* TextSimple options = new TextSimple()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextSimple {
protected String[] fields;
protected boolean operatorAnd;
protected String analyzer;
protected String flags;
protected boolean lowercaseExpandedTerms = true;
protected boolean analyzeWildcard;
protected String locale;
protected boolean lenient;
protected String minShouldMatch;
/**
* Construct
*/
public TextSimple() {
}
/**
* Set the fields.
*/
public TextSimple fields(String... fields) {
this.fields = fields;
return this;
}
/**
* Use AND as the default operator.
*/
public TextSimple opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use OR as the default operator.
*/
public TextSimple opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the analyzer
*/
public TextSimple analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the flags.
*/
public TextSimple flags(String flags) {
this.flags = flags;
return this;
}
/**
* Set the false to not use lowercase expanded terms.
*/
public TextSimple lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set to true to use analyze wildcard.
*/
public TextSimple analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the locale.
*/
public TextSimple locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set the lenient mode.
*/
public TextSimple lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextSimple minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return true to analyse wildcard.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return true if the default operator should be AND.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer to use.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return the flags.
*/
public String getFlags() {
return flags;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
}
@@ -5,6 +5,9 @@ import com.avaje.ebean.TextExpressionList;
import com.avaje.ebean.TextJunction;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiTextJunction;
@@ -58,6 +61,21 @@ class DTextJunction<T> extends JunctionExpression<T> implements SpiTextJunction<
return exprList.multiMatch(query, options);
}
@Override
public TextExpressionList<T> textSimple(String search, TextSimple options) {
return exprList.textSimple(search, options);
}
@Override
public TextExpressionList<T> textQueryString(String search, TextQueryString options) {
return exprList.textQueryString(search, options);
}
@Override
public TextExpressionList<T> textCommonTerms(String search, TextCommonTerms options) {
return exprList.textCommonTerms(search, options);
}
@Override
public TextJunction<T> must() {
return exprList.must();
@@ -12,6 +12,9 @@ import com.avaje.ebean.TextJunction;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -50,6 +53,21 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new TextMultiMatchExpression(query, options);
}
@Override
public Expression textSimple(String search, TextSimple options) {
return new TextSimpleExpression(search, options);
}
@Override
public Expression textQueryString(String search, TextQueryString options) {
return new TextQueryStringExpression(search, options);
}
@Override
public Expression textCommonTerms(String search, TextCommonTerms options) {
return new TextCommonTermsExpression(search, options);
}
public Expression jsonExists(String propertyName, String path) {
return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
}
@@ -4,6 +4,9 @@ import com.avaje.ebean.*;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
@@ -876,6 +879,24 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T>, SpiTextEx
return this;
}
@Override
public TextExpressionList<T> textSimple(String search, TextSimple options) {
add(expr.textSimple(search, options));
return this;
}
@Override
public TextExpressionList<T> textQueryString(String search, TextQueryString options) {
add(expr.textQueryString(search, options));
return this;
}
@Override
public TextExpressionList<T> textCommonTerms(String search, TextCommonTerms options) {
add(expr.textCommonTerms(search, options));
return this;
}
@Override
public TextJunction<T> must() {
return textJunction(TextJunction.Type.MUST);
@@ -4,6 +4,9 @@ import com.avaje.ebean.LikeType;
import com.avaje.ebean.TextJunction;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import java.io.IOException;
import java.util.List;
@@ -104,6 +107,21 @@ public interface DocQueryContext {
*/
void writeMultiMatch(String search, MultiMatch options) throws IOException;
/**
* Write a simple expression.
*/
void writeTextSimple(String search, TextSimple options) throws IOException;
/**
* Write a common terms expression.
*/
void writeTextCommonTerms(String search, TextCommonTerms options) throws IOException;
/**
* Write a query string expression.
*/
void writeTextQueryString(String search, TextQueryString options) throws IOException;
/**
* Start a Bool which may contain some of Must, Must Not, Should.
*/
@@ -0,0 +1,27 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.search.TextCommonTerms;
import java.io.IOException;
/**
* Full text common terms expression.
*/
class TextCommonTermsExpression extends AbstractTextExpression {
private final String search;
private final TextCommonTerms options;
public TextCommonTermsExpression(String search, TextCommonTerms options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextCommonTerms(search, options);
}
}
@@ -0,0 +1,27 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.search.TextQueryString;
import java.io.IOException;
/**
* Full text query string expression.
*/
class TextQueryStringExpression extends AbstractTextExpression {
private final String search;
private final TextQueryString options;
public TextQueryStringExpression(String search, TextQueryString options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextQueryString(search, options);
}
}
@@ -0,0 +1,27 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.search.TextSimple;
import java.io.IOException;
/**
* Full text Multi-Match expression.
*/
class TextSimpleExpression extends AbstractTextExpression {
private final String search;
private final TextSimple options;
public TextSimpleExpression(String search, TextSimple options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextSimple(search, options);
}
}