mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 10:38:57 +00:00
#594 - ElasticSearch - Add match / TextExpression support (Full text search expression)
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* An expression that is part of a WHERE or HAVING clause.
|
||||
*/
|
||||
public interface Expression extends Serializable {
|
||||
public interface Expression {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.search.Match;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -285,6 +287,11 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression raw(String raw);
|
||||
|
||||
/**
|
||||
* Create a Text Match expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textMatch(String propertyName, String search, Match options);
|
||||
|
||||
/**
|
||||
* And - join two expressions with a logical and.
|
||||
*/
|
||||
@@ -319,4 +326,12 @@ public interface ExpressionFactory {
|
||||
* Return a list of expressions that will be joined by OR's.
|
||||
*/
|
||||
<T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent);
|
||||
|
||||
/**
|
||||
* Return a Text query junction for MUST, SHOULD or MUST NOT.
|
||||
* <p>
|
||||
* This is doc store Elastic only.
|
||||
* </p>
|
||||
*/
|
||||
<T> TextJunction<T> textJunction(Query<T> query, TextExpressionList<T> parent, TextJunction.Type type);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.avaje.ebean;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -30,7 +29,7 @@ import java.util.Set;
|
||||
*
|
||||
* @see Query#where()
|
||||
*/
|
||||
public interface ExpressionList<T> extends Serializable {
|
||||
public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Return the query that owns this expression list.
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.avaje.ebean;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -1042,6 +1041,27 @@ public interface Query<T> {
|
||||
*/
|
||||
ExpressionList<T> where();
|
||||
|
||||
/**
|
||||
* Add Full text search expressions for Document store queries.
|
||||
* <p>
|
||||
* This is currently ElasticSearch only and provides the full text
|
||||
* expressions such as Match and Multi-Match.
|
||||
* </p>
|
||||
* <p>
|
||||
* This automatically makes this query a "Doc Store" query and will execute
|
||||
* against the document store (ElasticSearch).
|
||||
* </p>
|
||||
* <p>
|
||||
* Expressions added here are added to the "query" section of an ElasticSearch
|
||||
* query rather than the "filter" section.
|
||||
* </p>
|
||||
* <p>
|
||||
* Expressions added to the where() are added to the "filter" section of an
|
||||
* ElasticSearch query.
|
||||
* </p>
|
||||
*/
|
||||
TextExpressionList<T> text();
|
||||
|
||||
/**
|
||||
* This applies a filter on the 'many' property list rather than the root
|
||||
* level objects.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.search.Match;
|
||||
|
||||
/**
|
||||
* An list of Full text query expressions.
|
||||
* <p>
|
||||
* For ElasticSearch these expression go into the "query" section rather than the "filter" section.
|
||||
* </p>
|
||||
*/
|
||||
public interface TextExpressionList<T> extends ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Add a match expression.
|
||||
*
|
||||
* @param propertyName The property name for the match
|
||||
* @param search The search value
|
||||
*/
|
||||
TextExpressionList<T> match(String propertyName, String search);
|
||||
|
||||
/**
|
||||
* Add a match expression with options.
|
||||
*
|
||||
* @param propertyName The property name for the match
|
||||
* @param search The search value
|
||||
*/
|
||||
TextExpressionList<T> match(String propertyName, String search, Match options);
|
||||
|
||||
/**
|
||||
* Start a list of expressions that will be joined by MUST.
|
||||
*/
|
||||
TextJunction<T> must();
|
||||
|
||||
/**
|
||||
* Start a list of expressions that will be joined by SHOULD.
|
||||
*/
|
||||
TextJunction<T> should();
|
||||
|
||||
/**
|
||||
* Start a list of expressions that will be joined by MUST NOT.
|
||||
*/
|
||||
TextJunction<T> mustNot();
|
||||
|
||||
/**
|
||||
* End the list of MUST expressions.
|
||||
*/
|
||||
TextExpressionList<T> endMust();
|
||||
|
||||
/**
|
||||
* End the list of SHOULD expressions.
|
||||
*/
|
||||
TextExpressionList<T> endShould();
|
||||
|
||||
/**
|
||||
* End the list of MUST NOT expressions.
|
||||
*/
|
||||
TextExpressionList<T> endMustNot();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
/**
|
||||
* A Full text MUST, MUST NOT or SHOULD group of expressions.
|
||||
*/
|
||||
public interface TextJunction<T> extends Junction<T>, TextExpressionList<T> {
|
||||
|
||||
/**
|
||||
* The type of Junction used in full text expressions.
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
/**
|
||||
* Logically a AND group.
|
||||
*/
|
||||
MUST("must"),
|
||||
|
||||
/**
|
||||
* Logically a NOT group.
|
||||
*/
|
||||
MUST_NOT("must_not"),
|
||||
|
||||
/**
|
||||
* Logically a OR group.
|
||||
*/
|
||||
SHOULD("should");
|
||||
|
||||
String literal;
|
||||
|
||||
Type(String literal) {
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the literal value for this type.
|
||||
*/
|
||||
public String literal() {
|
||||
return literal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
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;
|
||||
|
||||
protected boolean phrase;
|
||||
|
||||
protected boolean phrasePrefix;
|
||||
|
||||
protected int maxExpansions;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Match phrase() {
|
||||
phrase = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to be a "Phrase Prefix" type expression.
|
||||
*/
|
||||
public Match phrasePrefix() {
|
||||
phrasePrefix = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zero terms.
|
||||
*/
|
||||
public Match zeroTerms(String zeroTerms) {
|
||||
this.zeroTerms = zeroTerms;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cutoff frequency.
|
||||
*/
|
||||
public Match cutoffFrequency(double cutoffFrequency) {
|
||||
this.cutoffFrequency = cutoffFrequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max expansions (for phrase prefix only).
|
||||
*/
|
||||
public Match maxExpansions(int maxExpansions) {
|
||||
this.maxExpansions = maxExpansions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Analyzer to use for this expression.
|
||||
*/
|
||||
public Match analyzer(String analyzer) {
|
||||
this.analyzer = analyzer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public String getZeroTerms() {
|
||||
return zeroTerms;
|
||||
}
|
||||
|
||||
public double getCutoffFrequency() {
|
||||
return cutoffFrequency;
|
||||
}
|
||||
|
||||
public boolean isPhrase() {
|
||||
return phrase;
|
||||
}
|
||||
|
||||
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;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@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);
|
||||
result = 31 * result + (phrasePrefix ? 1 : 0);
|
||||
result = 31 * result + maxExpansions;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -509,6 +509,11 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
SpiExpressionList<T> getHavingExpressions();
|
||||
|
||||
/**
|
||||
* Return the text expressions.
|
||||
*/
|
||||
SpiExpressionList<T> getTextExpression();
|
||||
|
||||
/**
|
||||
* Return additional having clause. Where raw String expressions are added
|
||||
* to having clause rather than Expression objects.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.TextExpressionList;
|
||||
|
||||
/**
|
||||
* SPI extensions to Full text expression list.
|
||||
*/
|
||||
public interface SpiTextExpressionList<T> extends TextExpressionList<T> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.TextJunction;
|
||||
import com.avaje.ebeaninternal.server.expression.DocQueryContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SPI extension to the full text junctions (MUST, MUST NOT, SHOULD).
|
||||
*/
|
||||
public interface SpiTextJunction<T> extends TextJunction<T> {
|
||||
|
||||
/**
|
||||
* Write the junction expression to the query context.
|
||||
*/
|
||||
void writeDocQueryJunction(DocQueryContext context) throws IOException;
|
||||
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
*/
|
||||
public abstract class AbstractExpression implements SpiExpression {
|
||||
|
||||
private static final long serialVersionUID = 4072786211853856174L;
|
||||
|
||||
protected final String propName;
|
||||
|
||||
protected AbstractExpression(String propName) {
|
||||
|
||||
@@ -15,8 +15,6 @@ import java.util.Map.Entry;
|
||||
|
||||
class AllEqualsExpression extends NonPrepareExpression {
|
||||
|
||||
private static final long serialVersionUID = -8691773558205937025L;
|
||||
|
||||
private final Map<String, Object> propMap;
|
||||
|
||||
AllEqualsExpression(Map<String, Object> propMap) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import java.io.IOException;
|
||||
|
||||
class BetweenExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = 2078918165221454910L;
|
||||
|
||||
private static final String BETWEEN = " between ";
|
||||
|
||||
private final Object valueHigh;
|
||||
|
||||
@@ -15,8 +15,6 @@ import java.io.IOException;
|
||||
*/
|
||||
class BetweenPropertyExpression extends NonPrepareExpression {
|
||||
|
||||
private static final long serialVersionUID = 2078918165221454910L;
|
||||
|
||||
private static final String BETWEEN = " between ";
|
||||
|
||||
private final String lowProperty;
|
||||
|
||||
-2
@@ -9,8 +9,6 @@ import java.io.IOException;
|
||||
|
||||
class CaseInsensitiveEqualExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = -6406036750998971064L;
|
||||
|
||||
private final String value;
|
||||
|
||||
CaseInsensitiveEqualExpression(String propertyName, String value) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.TextExpressionList;
|
||||
import com.avaje.ebean.TextJunction;
|
||||
import com.avaje.ebean.search.Match;
|
||||
import com.avaje.ebeaninternal.api.SpiExpression;
|
||||
import com.avaje.ebeaninternal.api.SpiTextJunction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of SpiTextJunction (Must, Must Not or Should group).
|
||||
*/
|
||||
class DTextJunction<T> extends JunctionExpression<T> implements SpiTextJunction<T> {
|
||||
|
||||
private final TextJunction.Type type;
|
||||
|
||||
DTextJunction(Query<T> query, TextExpressionList<T> parent, TextJunction.Type type) {
|
||||
super(query, parent);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQueryJunction(DocQueryContext context) throws IOException {
|
||||
context.startBoolGroupList(type);
|
||||
List<SpiExpression> list = exprList.internalList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
list.get(i).writeDocQuery(context);
|
||||
}
|
||||
context.endBoolGroupList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiExpression copyForPlanKey() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> match(String propertyName, String search) {
|
||||
return match(propertyName, search, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> match(String propertyName, String search, Match options) {
|
||||
return exprList.match(propertyName, search, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> must() {
|
||||
return exprList.must();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> should() {
|
||||
return exprList.should();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> mustNot() {
|
||||
return exprList.mustNot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endMust() {
|
||||
return endTextJunction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endShould() {
|
||||
return endTextJunction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endMustNot() {
|
||||
return endTextJunction();
|
||||
}
|
||||
|
||||
private TextExpressionList<T> endTextJunction() {
|
||||
return exprList.endTextJunction();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,8 +42,6 @@ import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
*/
|
||||
public class DefaultExampleExpression implements SpiExpression, ExampleExpression {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The example bean containing the properties.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,10 @@ import com.avaje.ebean.ExpressionList;
|
||||
import com.avaje.ebean.Junction;
|
||||
import com.avaje.ebean.LikeType;
|
||||
import com.avaje.ebean.Query;
|
||||
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.ebeaninternal.api.SpiExpressionFactory;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
|
||||
@@ -36,6 +39,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
return "sql";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression textMatch(String propertyName, String search, Match options) {
|
||||
return new TextMatchExpression(propertyName, search, options);
|
||||
}
|
||||
|
||||
public Expression jsonExists(String propertyName, String path) {
|
||||
return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
|
||||
}
|
||||
@@ -434,4 +442,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
return new JunctionExpression.Disjunction<T>(query, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a Full text junction (Must, Must Not or Should).
|
||||
*/
|
||||
@Override
|
||||
public <T> TextJunction<T> textJunction(Query<T> query, TextExpressionList<T> parent, TextJunction.Type type) {
|
||||
return new DTextJunction<T>(query, parent, type);
|
||||
}
|
||||
}
|
||||
|
||||
+125
-10
@@ -2,12 +2,15 @@ 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.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.ManyWhereJoins;
|
||||
import com.avaje.ebeaninternal.api.SpiExpression;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionList;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
|
||||
import com.avaje.ebeaninternal.api.SpiTextExpressionList;
|
||||
import com.avaje.ebeaninternal.api.SpiTextJunction;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -21,15 +24,13 @@ import java.util.Set;
|
||||
/**
|
||||
* Default implementation of ExpressionList.
|
||||
*/
|
||||
public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
|
||||
private static final long serialVersionUID = -6992345500247035947L;
|
||||
public class DefaultExpressionList<T> implements SpiExpressionList<T>, SpiTextExpressionList<T> {
|
||||
|
||||
protected final List<SpiExpression> list;
|
||||
|
||||
protected final Query<T> query;
|
||||
|
||||
protected final ExpressionList<T> parentExprList;
|
||||
protected final TextExpressionList<T> parentExprList;
|
||||
|
||||
protected transient ExpressionFactory expr;
|
||||
|
||||
@@ -37,15 +38,32 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
private final String listAndEnd;
|
||||
private final String listAndJoin;
|
||||
|
||||
public DefaultExpressionList(Query<T> query, ExpressionList<T> parentExprList) {
|
||||
/**
|
||||
* Set to true for the "Text" root expression list.
|
||||
*/
|
||||
private final boolean textRoot;
|
||||
|
||||
/**
|
||||
* Construct for Text root expression list - this handles implicit Bool Should, Must etc.
|
||||
*/
|
||||
public DefaultExpressionList(Query<T> query) {
|
||||
this(query, query.getExpressionFactory(), null, new ArrayList<SpiExpression>(), true);
|
||||
}
|
||||
|
||||
public DefaultExpressionList(Query<T> query, TextExpressionList<T> parentExprList) {
|
||||
this(query, query.getExpressionFactory(), parentExprList);
|
||||
}
|
||||
|
||||
public DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList) {
|
||||
public DefaultExpressionList(Query<T> query, ExpressionFactory expr, TextExpressionList<T> parentExprList) {
|
||||
this(query, expr, parentExprList, new ArrayList<SpiExpression>());
|
||||
}
|
||||
|
||||
protected DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list) {
|
||||
protected DefaultExpressionList(Query<T> query, ExpressionFactory expr, TextExpressionList<T> parentExprList, List<SpiExpression> list) {
|
||||
this(query, expr, parentExprList, list, false);
|
||||
}
|
||||
|
||||
private DefaultExpressionList(Query<T> query, ExpressionFactory expr, TextExpressionList<T> parentExprList, List<SpiExpression> list, boolean textRoot) {
|
||||
this.textRoot = textRoot;
|
||||
this.list = list;
|
||||
this.query = query;
|
||||
this.expr = expr;
|
||||
@@ -60,19 +78,66 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
this(null, null, null, new ArrayList<SpiExpression>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write being aware if it is the Top level "text" expressions.
|
||||
* <p>
|
||||
* If this is the Top level "text" expressions then it detects if explicit or implicit Bool Should, Must etc is required
|
||||
* to wrap the expressions.
|
||||
* </p>
|
||||
* <p>
|
||||
* If implicit Bool is required SHOULD is used.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) throws IOException {
|
||||
writeDocQuery(context, null);
|
||||
if (!textRoot) {
|
||||
writeDocQuery(context, null);
|
||||
|
||||
} else {
|
||||
// this is a Top level "text" expressions so we may need to wrap in Bool SHOULD etc.
|
||||
if (list.isEmpty()) throw new IllegalStateException("empty expression list?");
|
||||
|
||||
int size = list.size();
|
||||
|
||||
SpiExpression first = list.get(0);
|
||||
boolean explicitBool = first instanceof SpiTextJunction<?>;
|
||||
boolean implicitBool = !explicitBool && size > 1;
|
||||
|
||||
if (implicitBool || explicitBool) {
|
||||
context.startBoolGroup();
|
||||
}
|
||||
if (implicitBool) {
|
||||
context.startBoolGroupList(TextJunction.Type.SHOULD);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
SpiExpression expr = list.get(i);
|
||||
if (explicitBool) {
|
||||
try {
|
||||
((SpiTextJunction<?>) expr).writeDocQueryJunction(context);
|
||||
} catch (ClassCastException e) {
|
||||
throw new IllegalStateException("The top level text() expressions should be all be 'Must', 'Should' or 'Must Not' or none of them should be.", e);
|
||||
}
|
||||
} else {
|
||||
expr.writeDocQuery(context);
|
||||
}
|
||||
}
|
||||
if (implicitBool) {
|
||||
context.endBoolGroupList();
|
||||
}
|
||||
if (implicitBool || explicitBool) {
|
||||
context.endBoolGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException {
|
||||
|
||||
int size = list.size();
|
||||
if (size == 1 && idEquals == null) {
|
||||
// only 1 expression - skip bool must
|
||||
// only 1 expression - skip bool
|
||||
list.get(0).writeDocQuery(context);
|
||||
} else if (size == 0 && idEquals != null) {
|
||||
// only idEquals - skip bool must
|
||||
// only idEquals - skip bool
|
||||
idEquals.writeDocQuery(context);
|
||||
} else {
|
||||
// bool must wrap all the children
|
||||
@@ -139,6 +204,10 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return parentExprList == null ? this : parentExprList;
|
||||
}
|
||||
|
||||
protected TextExpressionList<T> endTextJunction() {
|
||||
return parentExprList == null ? this : parentExprList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> query() {
|
||||
return query;
|
||||
@@ -574,6 +643,12 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextJunction<T> textJunction(TextJunction.Type type) {
|
||||
TextJunction<T> junction = expr.textJunction(query, this, type);
|
||||
add(junction);
|
||||
return junction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Junction<T> conjunction() {
|
||||
Junction<T> conjunction = expr.conjunction(query, this);
|
||||
@@ -778,4 +853,44 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> match(String propertyName, String search) {
|
||||
return match(propertyName, search, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> match(String propertyName, String search, Match options) {
|
||||
add(expr.textMatch(propertyName, search, options));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> must() {
|
||||
return textJunction(TextJunction.Type.MUST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> should() {
|
||||
return textJunction(TextJunction.Type.SHOULD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextJunction<T> mustNot() {
|
||||
return textJunction(TextJunction.Type.MUST_NOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endMust() {
|
||||
return endTextJunction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endShould() {
|
||||
return endTextJunction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList<T> endMustNot() {
|
||||
return endTextJunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.LikeType;
|
||||
import com.avaje.ebean.TextJunction;
|
||||
import com.avaje.ebean.search.Match;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -90,4 +92,29 @@ public interface DocQueryContext {
|
||||
* Write a Like expression.
|
||||
*/
|
||||
void writeLike(String propName, String val, LikeType type, boolean caseInsensitive) throws IOException;
|
||||
|
||||
/**
|
||||
* Write a Match expression.
|
||||
*/
|
||||
void writeMatch(String propName, String search, Match options) throws IOException;
|
||||
|
||||
/**
|
||||
* Start a Bool which may contain some of Must, Must Not, Should.
|
||||
*/
|
||||
void startBoolGroup() throws IOException;
|
||||
|
||||
/**
|
||||
* Start a Must, Must Not or Should list.
|
||||
*/
|
||||
void startBoolGroupList(TextJunction.Type type) throws IOException;
|
||||
|
||||
/**
|
||||
* End a Must, Must Not or Should list.
|
||||
*/
|
||||
void endBoolGroupList() throws IOException;
|
||||
|
||||
/**
|
||||
* End the Bool group.
|
||||
*/
|
||||
void endBoolGroup() throws IOException;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import com.avaje.ebeaninternal.server.query.CQuery;
|
||||
|
||||
class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpression {
|
||||
|
||||
private static final long serialVersionUID = 666990277309851644L;
|
||||
|
||||
protected final boolean not;
|
||||
|
||||
protected final SpiQuery<?> subQuery;
|
||||
|
||||
@@ -10,8 +10,6 @@ import java.util.Set;
|
||||
|
||||
public class FilterExpressionList<T> extends DefaultExpressionList<T> {
|
||||
|
||||
private static final long serialVersionUID = 2226895827150099020L;
|
||||
|
||||
private static final String notAllowedMessage = "This method is not allowed on a filter";
|
||||
|
||||
private final Query<T> rootQuery;
|
||||
|
||||
@@ -14,8 +14,6 @@ import java.io.IOException;
|
||||
*/
|
||||
class IdExpression extends NonPrepareExpression implements SpiExpression {
|
||||
|
||||
private static final long serialVersionUID = -3065936341718489842L;
|
||||
|
||||
private final Object value;
|
||||
|
||||
IdExpression(Object value) {
|
||||
|
||||
@@ -16,8 +16,6 @@ import java.util.List;
|
||||
*/
|
||||
public class IdInExpression extends NonPrepareExpression {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<?> idList;
|
||||
|
||||
public IdInExpression(List<?> idList) {
|
||||
|
||||
@@ -11,8 +11,6 @@ import java.util.Collection;
|
||||
|
||||
class InExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = 3150665801693551260L;
|
||||
|
||||
private final boolean not;
|
||||
|
||||
private final Object[] values;
|
||||
|
||||
@@ -16,8 +16,6 @@ import java.util.List;
|
||||
*/
|
||||
class InQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression {
|
||||
|
||||
private static final long serialVersionUID = 666990277309851644L;
|
||||
|
||||
private final boolean not;
|
||||
|
||||
private final SpiQuery<?> subQuery;
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.Expression;
|
||||
import com.avaje.ebean.ExpressionList;
|
||||
import com.avaje.ebean.FetchPath;
|
||||
import com.avaje.ebean.FutureIds;
|
||||
import com.avaje.ebean.FutureList;
|
||||
import com.avaje.ebean.FutureRowCount;
|
||||
import com.avaje.ebean.Junction;
|
||||
import com.avaje.ebean.OrderBy;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.QueryEachConsumer;
|
||||
import com.avaje.ebean.QueryEachWhileConsumer;
|
||||
import com.avaje.ebean.QueryIterator;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebean.*;
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.ManyWhereJoins;
|
||||
@@ -34,16 +21,12 @@ import java.util.Set;
|
||||
*/
|
||||
abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, ExpressionList<T> {
|
||||
|
||||
private static final long serialVersionUID = -7422204102750462676L;
|
||||
static final String OR = " or ";
|
||||
|
||||
private static final String OR = " or ";
|
||||
|
||||
private static final String AND = " and ";
|
||||
static final String AND = " and ";
|
||||
|
||||
static class Conjunction<T> extends JunctionExpression<T> {
|
||||
|
||||
private static final long serialVersionUID = -645619859900030678L;
|
||||
|
||||
Conjunction(Query<T> query, ExpressionList<T> parent) {
|
||||
super(false, AND, query, parent);
|
||||
}
|
||||
@@ -59,8 +42,6 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
|
||||
static class Disjunction<T> extends JunctionExpression<T> {
|
||||
|
||||
private static final long serialVersionUID = -8464470066692221413L;
|
||||
|
||||
Disjunction(Query<T> query, ExpressionList<T> parent) {
|
||||
super(true, OR, query, parent);
|
||||
}
|
||||
@@ -84,10 +65,16 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
*/
|
||||
private final boolean disjunction;
|
||||
|
||||
JunctionExpression(Query<T> query, TextExpressionList<T> parent) {
|
||||
this.disjunction = true;
|
||||
this.joinType = OR;
|
||||
this.exprList = new DefaultExpressionList<T>(query, parent);
|
||||
}
|
||||
|
||||
JunctionExpression(boolean disjunction, String joinType, Query<T> query, ExpressionList<T> parent) {
|
||||
this.disjunction = disjunction;
|
||||
this.joinType = joinType;
|
||||
this.exprList = new DefaultExpressionList<T>(query, parent);
|
||||
this.exprList = new DefaultExpressionList<T>(query, (TextExpressionList<T>)parent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,8 +10,6 @@ import java.io.IOException;
|
||||
|
||||
class LikeExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = -5398151809111172380L;
|
||||
|
||||
private final String val;
|
||||
|
||||
private final boolean caseInsensitive;
|
||||
|
||||
@@ -16,15 +16,11 @@ import java.io.IOException;
|
||||
*/
|
||||
abstract class LogicExpression implements SpiExpression {
|
||||
|
||||
private static final long serialVersionUID = 616860781960645251L;
|
||||
|
||||
static final String AND = " and ";
|
||||
static final String OR = " or ";
|
||||
|
||||
static class And extends LogicExpression {
|
||||
|
||||
private static final long serialVersionUID = -3832889676798526444L;
|
||||
|
||||
And(Expression expOne, Expression expTwo) {
|
||||
super(AND, expOne, expTwo);
|
||||
}
|
||||
@@ -38,8 +34,6 @@ abstract class LogicExpression implements SpiExpression {
|
||||
|
||||
static class Or extends LogicExpression {
|
||||
|
||||
private static final long serialVersionUID = -6871993143194094819L;
|
||||
|
||||
Or(Expression expOne, Expression expTwo) {
|
||||
super(OR, expOne, expTwo);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.io.IOException;
|
||||
|
||||
final class NotExpression implements SpiExpression {
|
||||
|
||||
private static final long serialVersionUID = 5648926732402355781L;
|
||||
|
||||
private static final String NOT_START = "not (";
|
||||
private static final String NOT_END = ") ";
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.io.IOException;
|
||||
*/
|
||||
class NullExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = 4246991057451128269L;
|
||||
|
||||
private final boolean notNull;
|
||||
|
||||
NullExpression(String propertyName, boolean notNull) {
|
||||
|
||||
@@ -11,8 +11,6 @@ import java.io.IOException;
|
||||
|
||||
class RawExpression extends NonPrepareExpression {
|
||||
|
||||
private static final long serialVersionUID = 7973903141340334606L;
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final Object[] values;
|
||||
|
||||
@@ -10,8 +10,6 @@ import java.io.IOException;
|
||||
|
||||
public class SimpleExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = -382881395755603790L;
|
||||
|
||||
private final Op type;
|
||||
|
||||
private final Object value;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.search.Match;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.SpiExpression;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Full text MATCH expression.
|
||||
*/
|
||||
public class TextMatchExpression extends AbstractExpression {
|
||||
|
||||
private final String search;
|
||||
|
||||
private final Match options;
|
||||
|
||||
public TextMatchExpression(String propertyName, String search, Match options) {
|
||||
super(propertyName);
|
||||
this.search = search;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) throws IOException {
|
||||
context.writeMatch(propName, 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(TextMatchExpression.class).add(propName).add(search);
|
||||
if (options != null) {
|
||||
builder.add(options.isAnd());
|
||||
builder.add(options.getBoost());
|
||||
builder.add(options.getMinShouldMatch());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int queryBindHash() {
|
||||
return search.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof TextMatchExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TextMatchExpression that = (TextMatchExpression) other;
|
||||
return this.propName.equals(that.propName)
|
||||
&& this.search.equals(that.search)
|
||||
&& this.options == null ? that.options == null : options.equals(that.options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
TextMatchExpression that = (TextMatchExpression) other;
|
||||
return search.equals(that.search);
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
*/
|
||||
private BindParams bindParams;
|
||||
|
||||
private DefaultExpressionList<T> textExpressions;
|
||||
|
||||
private DefaultExpressionList<T> whereExpressions;
|
||||
|
||||
private DefaultExpressionList<T> havingExpressions;
|
||||
@@ -1313,6 +1315,15 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextExpressionList text() {
|
||||
if (textExpressions == null) {
|
||||
useDocStore = true;
|
||||
textExpressions = new DefaultExpressionList<T>(this);
|
||||
}
|
||||
return textExpressions;
|
||||
}
|
||||
|
||||
public ExpressionList<T> where() {
|
||||
if (whereExpressions == null) {
|
||||
whereExpressions = new DefaultExpressionList<T>(this, null);
|
||||
@@ -1352,6 +1363,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return whereExpressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiExpressionList<T> getTextExpression() {
|
||||
return textExpressions;
|
||||
}
|
||||
|
||||
public String getGeneratedSql() {
|
||||
return generatedSql;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
|
||||
import com.avaje.ebean.search.Match;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TextExpressionListTest {
|
||||
|
||||
@Test
|
||||
public void syntax() {
|
||||
|
||||
Ebean.find(Order.class)
|
||||
.text().match("name", "rob");
|
||||
|
||||
|
||||
Ebean.find(Order.class)
|
||||
.text().should()
|
||||
.match("name", "rob")
|
||||
.match("note", "war and peace");
|
||||
|
||||
|
||||
Ebean.find(Order.class)
|
||||
.text()
|
||||
.should()
|
||||
.match("title", "war and peace")
|
||||
.match("author", "leo tolstoy")
|
||||
.should()
|
||||
.match("translator", "Constance Garnett")
|
||||
.match("translator", "Louise Maude");
|
||||
|
||||
|
||||
Ebean.find(Order.class)
|
||||
.text()
|
||||
.should()
|
||||
.match("title", "war and peace")
|
||||
.match("author", "leo tolstoy")
|
||||
.should()
|
||||
.match("translator", "Constance Garnett", Match.AND().boost(2).minShouldMatch("75%"))
|
||||
.match("translator", "Louise Maude")
|
||||
.where()
|
||||
.gt("reviewDate", 12345);
|
||||
|
||||
|
||||
Ebean.find(Order.class)
|
||||
.text()
|
||||
.must()
|
||||
.match("title", "quick")
|
||||
.endMust()
|
||||
.should()
|
||||
.match("title", "brown")
|
||||
.match("title", "dog")
|
||||
.endShould()
|
||||
.mustNot()
|
||||
.match("title", "lazy")
|
||||
.endMustNot()
|
||||
.where()
|
||||
.gt("reviewDate", 12345);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.avaje.ebean.search;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class MatchTest {
|
||||
|
||||
Match match() {
|
||||
return Match.AND()
|
||||
.analyzer("whitespace")
|
||||
.boost(2)
|
||||
.cutoffFrequency(1)
|
||||
.minShouldMatch("50%")
|
||||
.maxExpansions(3)
|
||||
.zeroTerms("all");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_when_allSet() {
|
||||
assertEquals(match(), match());
|
||||
assertEquals(match().hashCode(), match().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_analyzer() {
|
||||
assertNotEquals(match(), match().analyzer("foo"));
|
||||
assertNotEquals(match().hashCode(), match().analyzer("foo").hashCode());
|
||||
assertNotEquals(match(), match().analyzer(null));
|
||||
assertNotEquals(match().hashCode(), match().analyzer(null).hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_boost() {
|
||||
assertNotEquals(match(), match().boost(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_cutoffFrequency() {
|
||||
assertNotEquals(match(), match().cutoffFrequency(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_maxExpansions() {
|
||||
assertNotEquals(match(), match().maxExpansions(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_minShouldMatch() {
|
||||
assertNotEquals(match(), match().minShouldMatch("12%"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_zeroTerms() {
|
||||
assertNotEquals(match(), match().zeroTerms("none"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_phrase() {
|
||||
assertNotEquals(match(), match().phrase());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_phrasePrefix() {
|
||||
assertNotEquals(match(), match().phrasePrefix());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notEquals_when_operator() {
|
||||
assertNotEquals(match(), match().opOr());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user