diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java
index 884bab4db..9c239d870 100644
--- a/src/main/java/com/avaje/ebean/ExpressionFactory.java
+++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java
@@ -1,6 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.search.Match;
+import com.avaje.ebean.search.MultiMatch;
import java.util.Collection;
import java.util.List;
@@ -292,6 +293,11 @@ public interface ExpressionFactory {
*/
Expression textMatch(String propertyName, String search, Match options);
+ /**
+ * Create a Text Multi match expression (currently doc store/Elastic only).
+ */
+ Expression textMultiMatch(String query, MultiMatch options);
+
/**
* And - join two expressions with a logical and.
*/
@@ -334,4 +340,5 @@ public interface ExpressionFactory {
*
*/
TextJunction textJunction(Query query, TextExpressionList parent, TextJunction.Type type);
+
}
diff --git a/src/main/java/com/avaje/ebean/TextExpressionList.java b/src/main/java/com/avaje/ebean/TextExpressionList.java
index a30fc7179..b10021e4f 100644
--- a/src/main/java/com/avaje/ebean/TextExpressionList.java
+++ b/src/main/java/com/avaje/ebean/TextExpressionList.java
@@ -1,6 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.search.Match;
+import com.avaje.ebean.search.MultiMatch;
/**
* An list of Full text query expressions.
@@ -26,6 +27,16 @@ public interface TextExpressionList extends ExpressionList {
*/
TextExpressionList match(String propertyName, String search, Match options);
+ /**
+ * Add a multi-match expression.
+ */
+ TextExpressionList multiMatch(String search, String... properties);
+
+ /**
+ * Add a multi-match expression using options.
+ */
+ TextExpressionList multiMatch(String search, MultiMatch options);
+
/**
* Start a list of expressions that will be joined by MUST.
*/
@@ -55,4 +66,5 @@ public interface TextExpressionList extends ExpressionList {
* End the list of MUST NOT expressions.
*/
TextExpressionList endMustNot();
+
}
diff --git a/src/main/java/com/avaje/ebean/search/BaseMatch.java b/src/main/java/com/avaje/ebean/search/BaseMatch.java
new file mode 100644
index 000000000..90daf51d2
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/search/BaseMatch.java
@@ -0,0 +1,130 @@
+package com.avaje.ebean.search;
+
+/**
+ * Options for the text match expression.
+ */
+public abstract class BaseMatch {
+
+ protected boolean and;
+
+ protected String analyzer;
+
+ protected double boost;
+
+ protected String minShouldMatch;
+
+ protected int maxExpansions;
+
+ protected String zeroTerms;
+
+ protected double cutoffFrequency;
+
+ protected String fuzziness;
+
+ protected int prefixLength;
+
+ protected String rewrite;
+
+ /**
+ * Return true if using the AND operator otherwise using the OR operator.
+ */
+ public boolean isAnd() {
+ return and;
+ }
+
+ /**
+ * Return the boost.
+ */
+ public double getBoost() {
+ return boost;
+ }
+
+ /**
+ * Return the minimum should match.
+ */
+ public String getMinShouldMatch() {
+ return minShouldMatch;
+ }
+
+ /**
+ * Return the zero terms option.
+ */
+ public String getZeroTerms() {
+ return zeroTerms;
+ }
+
+ /**
+ * Return the cutoff frequency.
+ */
+ public double getCutoffFrequency() {
+ return cutoffFrequency;
+ }
+
+ /**
+ * Return the max expansions.
+ */
+ public int getMaxExpansions() {
+ return maxExpansions;
+ }
+
+ /**
+ * Return the analyzer.
+ */
+ public String getAnalyzer() {
+ return analyzer;
+ }
+
+ /**
+ * Return the fuzziness.
+ */
+ public String getFuzziness() {
+ return fuzziness;
+ }
+
+ /**
+ * Return the prefix length.
+ */
+ public int getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Return the rewrite option.
+ */
+ public String getRewrite() {
+ return rewrite;
+ }
+
+ protected boolean baseEquals(BaseMatch baseMatch) {
+
+ if (and != baseMatch.and) return false;
+ if (Double.compare(baseMatch.boost, boost) != 0) return false;
+ if (maxExpansions != baseMatch.maxExpansions) return false;
+ if (Double.compare(baseMatch.cutoffFrequency, cutoffFrequency) != 0) return false;
+ if (prefixLength != baseMatch.prefixLength) return false;
+ if (analyzer != null ? !analyzer.equals(baseMatch.analyzer) : baseMatch.analyzer != null) return false;
+ if (minShouldMatch != null ? !minShouldMatch.equals(baseMatch.minShouldMatch) : baseMatch.minShouldMatch != null) return false;
+ if (zeroTerms != null ? !zeroTerms.equals(baseMatch.zeroTerms) : baseMatch.zeroTerms != null) return false;
+ if (fuzziness != null ? !fuzziness.equals(baseMatch.fuzziness) : baseMatch.fuzziness != null) return false;
+ return rewrite != null ? rewrite.equals(baseMatch.rewrite) : baseMatch.rewrite == null;
+ }
+
+ protected int baseHashCode() {
+
+ int result;
+ long temp;
+ result = (and ? 1 : 0);
+ result = 31 * result + (analyzer != null ? analyzer.hashCode() : 0);
+ temp = Double.doubleToLongBits(boost);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + (minShouldMatch != null ? minShouldMatch.hashCode() : 0);
+ result = 31 * result + maxExpansions;
+ result = 31 * result + (zeroTerms != null ? zeroTerms.hashCode() : 0);
+ temp = Double.doubleToLongBits(cutoffFrequency);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + (fuzziness != null ? fuzziness.hashCode() : 0);
+ result = 31 * result + prefixLength;
+ result = 31 * result + (rewrite != null ? rewrite.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/search/Match.java b/src/main/java/com/avaje/ebean/search/Match.java
index 4de641ea6..391d7c184 100644
--- a/src/main/java/com/avaje/ebean/search/Match.java
+++ b/src/main/java/com/avaje/ebean/search/Match.java
@@ -3,26 +3,12 @@ package com.avaje.ebean.search;
/**
* Options for the text match expression.
*/
-public class Match {
-
- protected boolean and;
-
- protected double boost;
-
- protected String minShouldMatch;
-
- protected String zeroTerms;
-
- protected double cutoffFrequency;
-
- protected String analyzer;
+public class Match extends BaseMatch {
protected boolean phrase;
protected boolean phrasePrefix;
- protected int maxExpansions;
-
/**
* Create and return Match options using AND operator.
*/
@@ -37,38 +23,6 @@ public class Match {
return new Match().opOr();
}
- /**
- * Use the AND operator (rather than OR).
- */
- public Match opAnd() {
- and = true;
- return this;
- }
-
- /**
- * Use the OR operator (rather than AND).
- */
- public Match opOr() {
- and = false;
- return this;
- }
-
- /**
- * Set the minimum should match value.
- */
- public Match minShouldMatch(String minShouldMatch) {
- this.minShouldMatch = minShouldMatch;
- return this;
- }
-
- /**
- * Set the boost.
- */
- public Match boost(double boost) {
- this.boost = boost;
- return this;
- }
-
/**
* Set this to be a "Phrase" type expression.
*/
@@ -85,6 +39,22 @@ public class Match {
return this;
}
+ /**
+ * Use the AND operator (rather than OR).
+ */
+ public Match opAnd() {
+ and = true;
+ return this;
+ }
+
+ /**
+ * Use the OR operator (rather than AND).
+ */
+ public Match opOr() {
+ and = false;
+ return this;
+ }
+
/**
* Set the zero terms.
*/
@@ -118,50 +88,43 @@ public class Match {
}
/**
- * Return true if using the AND operator otherwise using the OR operator.
+ * Set the boost.
*/
- public boolean isAnd() {
- return and;
+ public Match boost(double boost) {
+ this.boost = boost;
+ return this;
}
/**
- * Return the boost.
+ * Set the rewrite to use.
*/
- public double getBoost() {
- return boost;
+ public Match minShouldMatch(String minShouldMatch) {
+ this.minShouldMatch = minShouldMatch;
+ return this;
}
/**
- * Return the minimum should match.
+ * Set the rewrite to use.
*/
- public String getMinShouldMatch() {
- return minShouldMatch;
- }
-
- public String getZeroTerms() {
- return zeroTerms;
- }
-
- public double getCutoffFrequency() {
- return cutoffFrequency;
+ public Match rewrite(String rewrite) {
+ this.rewrite = rewrite;
+ return this;
}
+ /**
+ * Return true if this is a phrase query.
+ */
public boolean isPhrase() {
return phrase;
}
+ /**
+ * Return true if this is a phrase prefix query.
+ */
public boolean isPhrasePrefix() {
return phrasePrefix;
}
- public int getMaxExpansions() {
- return maxExpansions;
- }
-
- public String getAnalyzer() {
- return analyzer;
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -169,33 +132,16 @@ public class Match {
Match match = (Match) o;
- if (and != match.and) return false;
- if (Double.compare(match.boost, boost) != 0) return false;
- if (Double.compare(match.cutoffFrequency, cutoffFrequency) != 0) return false;
if (phrase != match.phrase) return false;
if (phrasePrefix != match.phrasePrefix) return false;
- if (maxExpansions != match.maxExpansions) return false;
- if (minShouldMatch != null ? !minShouldMatch.equals(match.minShouldMatch) : match.minShouldMatch != null)
- return false;
- if (zeroTerms != null ? !zeroTerms.equals(match.zeroTerms) : match.zeroTerms != null) return false;
- return analyzer != null ? analyzer.equals(match.analyzer) : match.analyzer == null;
+ return baseEquals(match);
}
@Override
public int hashCode() {
- int result;
- long temp;
- result = (and ? 1 : 0);
- temp = Double.doubleToLongBits(boost);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- result = 31 * result + (minShouldMatch != null ? minShouldMatch.hashCode() : 0);
- result = 31 * result + (zeroTerms != null ? zeroTerms.hashCode() : 0);
- temp = Double.doubleToLongBits(cutoffFrequency);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- result = 31 * result + (analyzer != null ? analyzer.hashCode() : 0);
- result = 31 * result + (phrase ? 1 : 0);
+ int result = (phrase ? 1 : 0);
result = 31 * result + (phrasePrefix ? 1 : 0);
- result = 31 * result + maxExpansions;
+ result = 31 * result + baseHashCode();
return result;
}
}
diff --git a/src/main/java/com/avaje/ebean/search/MultiMatch.java b/src/main/java/com/avaje/ebean/search/MultiMatch.java
new file mode 100644
index 000000000..175b6469c
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/search/MultiMatch.java
@@ -0,0 +1,186 @@
+package com.avaje.ebean.search;
+
+import java.util.Arrays;
+
+/**
+ * Options for the text match expression.
+ */
+public class MultiMatch extends BaseMatch {
+
+ /**
+ * The MultiMatch type.
+ */
+ public enum Type {
+ BEST_FIELDS,
+ MOST_FIELDS,
+ CROSS_FIELDS,
+ PHRASE,
+ PHRASE_PREFIX
+ }
+
+ protected Type type = Type.BEST_FIELDS;
+
+ protected String[] fields;
+
+ protected double tieBreaker;
+
+ /**
+ * Create and return Match options using AND operator.
+ */
+ 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.
+ */
+ public MultiMatch(String... fields) {
+ this.fields = fields;
+ }
+
+ /**
+ * Set the type of query.
+ */
+ public MultiMatch type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Set the tieBreaker to use.
+ */
+ public MultiMatch tieBreaker(double tieBreaker) {
+ this.tieBreaker = tieBreaker;
+ return this;
+ }
+
+ /**
+ * Use the AND operator (rather than OR).
+ */
+ public MultiMatch opAnd() {
+ and = true;
+ return this;
+ }
+
+ /**
+ * Use the OR operator (rather than AND).
+ */
+ public MultiMatch opOr() {
+ and = false;
+ return this;
+ }
+
+ /**
+ * Set the minimum should match value.
+ */
+ public MultiMatch minShouldMatch(String minShouldMatch) {
+ this.minShouldMatch = minShouldMatch;
+ return this;
+ }
+
+ /**
+ * Set the boost.
+ */
+ public MultiMatch boost(double boost) {
+ this.boost = boost;
+ return this;
+ }
+
+ /**
+ * Set the zero terms.
+ */
+ public MultiMatch zeroTerms(String zeroTerms) {
+ this.zeroTerms = zeroTerms;
+ return this;
+ }
+
+ /**
+ * Set the cutoff frequency.
+ */
+ public MultiMatch cutoffFrequency(double cutoffFrequency) {
+ this.cutoffFrequency = cutoffFrequency;
+ return this;
+ }
+
+ /**
+ * Set the max expansions (for phrase prefix only).
+ */
+ public MultiMatch maxExpansions(int maxExpansions) {
+ this.maxExpansions = maxExpansions;
+ return this;
+ }
+
+ /**
+ * Set the Analyzer to use for this expression.
+ */
+ public MultiMatch analyzer(String analyzer) {
+ this.analyzer = analyzer;
+ return this;
+ }
+
+ /**
+ * Set the rewrite to use.
+ */
+ public MultiMatch rewrite(String rewrite) {
+ this.rewrite = rewrite;
+ return this;
+ }
+
+ /**
+ * Return the type.
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Return the fields to search.
+ */
+ public String[] getFields() {
+ return fields;
+ }
+
+ /**
+ * Return the tie breaker.
+ */
+ public double getTieBreaker() {
+ return tieBreaker;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MultiMatch that = (MultiMatch) o;
+
+ if (Double.compare(that.tieBreaker, tieBreaker) != 0) return false;
+ if (type != that.type) return false;
+ if (fields.length != that.fields.length) return false;
+ for (int i = 0; i < fields.length; i++) {
+ if (!fields[i].equals(that.fields[i])) {
+ return false;
+ }
+ }
+ return baseEquals(that);
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = type.hashCode();
+ result = 31 * result + Arrays.hashCode(fields);
+ temp = Double.doubleToLongBits(tieBreaker);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + baseHashCode();
+ return result;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DTextJunction.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DTextJunction.java
index baba32c88..50a66c8f7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DTextJunction.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DTextJunction.java
@@ -4,6 +4,7 @@ import com.avaje.ebean.Query;
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.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiTextJunction;
@@ -47,6 +48,16 @@ class DTextJunction extends JunctionExpression implements SpiTextJunction<
return exprList.match(propertyName, search, options);
}
+ @Override
+ public TextExpressionList multiMatch(String query, String... properties) {
+ return exprList.multiMatch(query, properties);
+ }
+
+ @Override
+ public TextExpressionList multiMatch(String query, MultiMatch options) {
+ return exprList.multiMatch(query, options);
+ }
+
@Override
public TextJunction must() {
return exprList.must();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
index 4a9bcd512..841f259ff 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
@@ -11,6 +11,7 @@ import com.avaje.ebean.TextExpressionList;
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.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -44,6 +45,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new TextMatchExpression(propertyName, search, options);
}
+ @Override
+ public Expression textMultiMatch(String query, MultiMatch options) {
+ return new TextMultiMatchExpression(query, options);
+ }
+
public Expression jsonExists(String propertyName, String path) {
return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
index fae3ae3e5..7f8fc4242 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
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.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
@@ -864,6 +865,17 @@ public class DefaultExpressionList implements SpiExpressionList, SpiTextEx
return this;
}
+ @Override
+ public TextExpressionList multiMatch(String query, String... fields) {
+ return multiMatch(query, new MultiMatch().fields(fields));
+ }
+
+ @Override
+ public TextExpressionList multiMatch(String query, MultiMatch options) {
+ add(expr.textMultiMatch(query, options));
+ return this;
+ }
+
@Override
public TextJunction must() {
return textJunction(TextJunction.Type.MUST);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
index 6943fa0c6..8a66d3bf1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.LikeType;
import com.avaje.ebean.TextJunction;
import com.avaje.ebean.search.Match;
+import com.avaje.ebean.search.MultiMatch;
import java.io.IOException;
import java.util.List;
@@ -98,6 +99,11 @@ public interface DocQueryContext {
*/
void writeMatch(String propName, String search, Match options) throws IOException;
+ /**
+ * Write a Multi-match expression.
+ */
+ void writeMultiMatch(String search, MultiMatch options) throws IOException;
+
/**
* Start a Bool which may contain some of Must, Must Not, Should.
*/
@@ -117,4 +123,5 @@ public interface DocQueryContext {
* End the Bool group.
*/
void endBoolGroup() throws IOException;
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/TextMatchExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/TextMatchExpression.java
index 47dc9e6f2..850382727 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/TextMatchExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/TextMatchExpression.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.expression;
+import com.avaje.ebean.search.BaseMatch;
import com.avaje.ebean.search.Match;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
@@ -44,9 +45,9 @@ public class TextMatchExpression extends AbstractExpression {
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(TextMatchExpression.class).add(propName).add(search);
if (options != null) {
- builder.add(options.isAnd());
- builder.add(options.getBoost());
- builder.add(options.getMinShouldMatch());
+ builder.add(options.isPhrase());
+ builder.add(options.isPhrasePrefix());
+ addHash(builder, options);
}
}
@@ -72,4 +73,20 @@ public class TextMatchExpression extends AbstractExpression {
TextMatchExpression that = (TextMatchExpression) other;
return search.equals(that.search);
}
+
+ /**
+ * Add the hash to the builder for the base/common options.
+ */
+ public static void addHash(HashQueryPlanBuilder builder, BaseMatch options) {
+ builder.add(options.isAnd());
+ builder.add(options.getAnalyzer());
+ builder.add(options.getBoost());
+ builder.add(options.getCutoffFrequency());
+ builder.add(options.getFuzziness());
+ builder.add(options.getMaxExpansions());
+ builder.add(options.getMinShouldMatch());
+ builder.add(options.getPrefixLength());
+ builder.add(options.getRewrite());
+ builder.add(options.getZeroTerms());
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/TextMultiMatchExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/TextMultiMatchExpression.java
new file mode 100644
index 000000000..7eee078e2
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/TextMultiMatchExpression.java
@@ -0,0 +1,77 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.search.MultiMatch;
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+
+import java.io.IOException;
+
+/**
+ * Full text Multi-Match expression.
+ */
+public class TextMultiMatchExpression extends AbstractExpression {
+
+ private final String search;
+
+ private final MultiMatch options;
+
+ public TextMultiMatchExpression(String search, MultiMatch options) {
+ super(null);
+ this.search = search;
+ this.options = options;
+ }
+
+ @Override
+ public void writeDocQuery(DocQueryContext context) throws IOException {
+ context.writeMultiMatch(search, options);
+ }
+
+ @Override
+ public void addSql(SpiExpressionRequest request) {
+ throw new IllegalStateException("Not implemented - DocStore/Elastic only");
+ }
+
+ @Override
+ public void addBindValues(SpiExpressionRequest request) {
+ throw new IllegalStateException("Not implemented - DocStore/Elastic only");
+ }
+
+ /**
+ * Based on the type and propertyName.
+ */
+ @Override
+ public void queryPlanHash(HashQueryPlanBuilder builder) {
+ builder.add(TextMultiMatchExpression.class).add(search);
+ builder.add(options.getType());
+ builder.add(options.getTieBreaker());
+ String[] fields = options.getFields();
+ builder.add(fields.length);
+ for (int i = 0; i < fields.length; i++) {
+ builder.add(fields[i]);
+ }
+ TextMatchExpression.addHash(builder, options);
+ }
+
+ @Override
+ public int queryBindHash() {
+ return search.hashCode();
+ }
+
+ @Override
+ public boolean isSameByPlan(SpiExpression other) {
+ if (!(other instanceof TextMultiMatchExpression)) {
+ return false;
+ }
+
+ TextMultiMatchExpression that = (TextMultiMatchExpression) other;
+ return this.search.equals(that.search)
+ && this.options == null ? that.options == null : options.equals(that.options);
+ }
+
+ @Override
+ public boolean isSameByBind(SpiExpression other) {
+ TextMultiMatchExpression that = (TextMultiMatchExpression) other;
+ return search.equals(that.search);
+ }
+}
diff --git a/src/test/java/com/avaje/ebean/TextExpressionListTest.java b/src/test/java/com/avaje/ebean/TextExpressionListTest.java
index 41a9770e1..c6b136323 100644
--- a/src/test/java/com/avaje/ebean/TextExpressionListTest.java
+++ b/src/test/java/com/avaje/ebean/TextExpressionListTest.java
@@ -2,6 +2,7 @@ package com.avaje.ebean;
import com.avaje.ebean.search.Match;
+import com.avaje.ebean.search.MultiMatch;
import com.avaje.tests.model.basic.Order;
import org.junit.Test;
@@ -59,4 +60,21 @@ public class TextExpressionListTest {
}
+
+ @Test
+ public void syntax_multiMatch() {
+
+ Ebean.find(Order.class)
+ .text()
+ .multiMatch("Will Smith", "title", "*name");
+
+ MultiMatch match = MultiMatch.fields("title", "*name")
+ .opAnd()
+ .type(MultiMatch.Type.PHRASE_PREFIX);
+
+ Ebean.find(Order.class)
+ .text()
+ .multiMatch("Will Smith", match);
+
+ }
}
\ No newline at end of file