You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2012/03/12 14:14:41 UTC
svn commit: r1299665 - in /lucene/dev/branches/branch_3x: ./ solr/
solr/CHANGES.txt solr/core/
solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java
solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java
Author: janhoy
Date: Mon Mar 12 13:14:40 2012
New Revision: 1299665
URL: http://svn.apache.org/viewvc?rev=1299665&view=rev
Log:
SOLR-3026: eDismax: Locking down which fields can be explicitly queried (user fields aka uf)
Modified:
lucene/dev/branches/branch_3x/ (props changed)
lucene/dev/branches/branch_3x/solr/ (props changed)
lucene/dev/branches/branch_3x/solr/CHANGES.txt
lucene/dev/branches/branch_3x/solr/core/ (props changed)
lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java
lucene/dev/branches/branch_3x/solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java
Modified: lucene/dev/branches/branch_3x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/CHANGES.txt?rev=1299665&r1=1299664&r2=1299665&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_3x/solr/CHANGES.txt Mon Mar 12 13:14:40 2012
@@ -110,6 +110,9 @@ New Features
* SOLR-2202: Currency FieldType, whith support for currencies and exchange rates
(Greg Fodor & Andrew Morrison via janhoy, rmuir, Uwe Schindler)
+* SOLR-3026: eDismax: Locking down which fields can be explicitly queried (user fields aka uf)
+ (janhoy, hossmann, Tomás Fernández Löbbe)
+
Optimizations
----------------------
* SOLR-1931: Speedup for LukeRequestHandler and admin/schema browser. New parameter
Modified: lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java?rev=1299665&r1=1299664&r2=1299665&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java (original)
+++ lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParserPlugin.java Mon Mar 12 13:14:40 2012
@@ -79,7 +79,10 @@ class ExtendedDismaxQParser extends QPar
/** shorten the class references for utilities */
private static interface DMP extends DisMaxParams {
- /* :NOOP */
+ /**
+ * User fields. The fields that can be used by the end user to create field-specific queries.
+ */
+ public static String UF = "uf";
}
@@ -87,15 +90,30 @@ class ExtendedDismaxQParser extends QPar
super(qstr, localParams, params, req);
}
- Map<String,Float> queryFields;
- Query parsedUserQuery;
+ /**
+ * The field names specified by 'qf' that (most) clauses will
+ * be queried against
+ */
+ private Map<String,Float> queryFields;
+
+ /**
+ * The field names specified by 'uf' that users are
+ * allowed to include literally in their query string. The Float
+ * boost values will be applied automaticly to any clause using that
+ * field name. '*' will be treated as an alias for any
+ * field that exists in the schema. Wildcards are allowed to
+ * express dynamicFields.
+ */
+ private UserFields userFields;
+ private Query parsedUserQuery;
private String[] boostParams;
private String[] multBoosts;
private List<Query> boostQueries;
private Query altUserQuery;
private QParser altQParser;
+ private SolrParams solrParams;
@Override
@@ -103,8 +121,10 @@ class ExtendedDismaxQParser extends QPar
SolrParams localParams = getLocalParams();
SolrParams params = getParams();
- SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
+ solrParams = SolrParams.wrapDefaults(localParams, params);
+ userFields = new UserFields(U.parseFieldBoosts(solrParams.getParams(DMP.UF)));
+
queryFields = SolrPluginUtils.parseFieldBoosts(solrParams.getParams(DisMaxParams.QF));
if (0 == queryFields.size()) {
queryFields.put(req.getSchema().getDefaultSearchFieldName(), 1.0f);
@@ -159,75 +179,59 @@ class ExtendedDismaxQParser extends QPar
new ExtendedSolrQueryParser(this, IMPOSSIBLE_FIELD_NAME);
up.addAlias(IMPOSSIBLE_FIELD_NAME,
tiebreaker, queryFields);
+ addAliasesFromRequest(up, tiebreaker);
up.setPhraseSlop(qslop); // slop for explicit user phrase queries
up.setAllowLeadingWildcard(true);
// defer escaping and only do if lucene parsing fails, or we need phrases
// parsing fails. Need to sloppy phrase queries anyway though.
List<Clause> clauses = null;
- boolean specialSyntax = false;
int numPluses = 0;
int numMinuses = 0;
- int numOptional = 0;
- int numAND = 0;
int numOR = 0;
int numNOT = 0;
- boolean sawLowerAnd=false;
- boolean sawLowerOr=false;
clauses = splitIntoClauses(userQuery, false);
for (Clause clause : clauses) {
- if (!clause.isPhrase && clause.hasSpecialSyntax) {
- specialSyntax = true;
- }
if (clause.must == '+') numPluses++;
if (clause.must == '-') numMinuses++;
if (clause.isBareWord()) {
String s = clause.val;
- if ("AND".equals(s)) {
- numAND++;
- } else if ("OR".equals(s)) {
+ if ("OR".equals(s)) {
numOR++;
} else if ("NOT".equals(s)) {
numNOT++;
- } else if (lowercaseOperators) {
- if ("and".equals(s)) {
- numAND++;
- sawLowerAnd=true;
- } else if ("or".equals(s)) {
- numOR++;
- sawLowerOr=true;
- }
+ } else if (lowercaseOperators && "or".equals(s)) {
+ numOR++;
}
}
}
- numOptional = clauses.size() - (numPluses + numMinuses);
- // convert lower or mixed case operators to uppercase if we saw them.
+ // Always rebuild mainUserQuery from clauses to catch modifications from splitIntoClauses
+ // This was necessary for userFields modifications to get propagated into the query.
+ // Convert lower or mixed case operators to uppercase if we saw them.
// only do this for the lucene query part and not for phrase query boosting
// since some fields might not be case insensitive.
// We don't use a regex for this because it might change and AND or OR in
// a phrase query in a case sensitive field.
- if (sawLowerAnd || sawLowerOr) {
- StringBuilder sb = new StringBuilder();
- for (int i=0; i<clauses.size(); i++) {
- Clause clause = clauses.get(i);
- String s = clause.raw;
- // and and or won't be operators at the start or end
- if (i>0 && i+1<clauses.size()) {
- if ("AND".equalsIgnoreCase(s)) {
- s="AND";
- } else if ("OR".equalsIgnoreCase(s)) {
- s="OR";
- }
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<clauses.size(); i++) {
+ Clause clause = clauses.get(i);
+ String s = clause.raw;
+ // and and or won't be operators at the start or end
+ if (i>0 && i+1<clauses.size()) {
+ if ("AND".equalsIgnoreCase(s)) {
+ s="AND";
+ } else if ("OR".equalsIgnoreCase(s)) {
+ s="OR";
}
- sb.append(s);
- sb.append(' ');
}
-
- mainUserQuery = sb.toString();
+ sb.append(s);
+ sb.append(' ');
}
-
+
+ mainUserQuery = sb.toString();
+
// For correct lucene queries, turn off mm processing if there
// were explicit operators (except for AND).
boolean doMinMatched = (numOR + numNOT + numPluses + numMinuses) == 0;
@@ -254,9 +258,8 @@ class ExtendedDismaxQParser extends QPar
}
}
-
if (parsedUserQuery == null) {
- StringBuilder sb = new StringBuilder();
+ sb = new StringBuilder();
for (Clause clause : clauses) {
boolean doQuote = clause.isPhrase;
@@ -280,6 +283,12 @@ class ExtendedDismaxQParser extends QPar
if (doQuote) {
sb.append('"');
}
+ if (clause.field != null) {
+ // Add the default user field boost, if any
+ Float boost = userFields.getBoost(clause.field);
+ if(boost != null)
+ sb.append("^").append(boost);
+ }
sb.append(' ');
}
String escapedUserQuery = sb.toString();
@@ -399,6 +408,28 @@ class ExtendedDismaxQParser extends QPar
}
/**
+ * Extracts all the alised fields from the requests and adds them to up
+ * @param up
+ * @param tiebreaker
+ * @throws ParseException
+ */
+ private void addAliasesFromRequest(ExtendedSolrQueryParser up, float tiebreaker) throws ParseException {
+ Iterator<String> it = solrParams.getParameterNamesIterator();
+ while(it.hasNext()) {
+ String param = it.next();
+ if(param.startsWith("f.") && param.endsWith(".qf")) {
+ // Add the alias
+ String fname = param.substring(2,param.length()-3);
+ String qfReplacement = solrParams.get(param);
+ Map<String,Float> parsedQf = SolrPluginUtils.parseFieldBoosts(qfReplacement);
+ if(parsedQf.size() == 0)
+ return;
+ up.addAlias(fname, tiebreaker, parsedQf);
+ }
+ }
+ }
+
+ /**
* Modifies the main query by adding a new optional Query consisting
* of shingled phrase queries across the specified clauses using the
* specified field => boost mappings.
@@ -603,6 +634,7 @@ class ExtendedDismaxQParser extends QPar
int end=s.length();
char ch=0;
int start;
+ boolean disallowUserField = false;
outer: while (pos < end) {
ch = s.charAt(pos);
@@ -619,6 +651,10 @@ class ExtendedDismaxQParser extends QPar
}
clause.field = getFieldName(s, pos, end);
+ if(clause.field != null && !userFields.isAllowed(clause.field)) {
+ disallowUserField = true;
+ clause.field = null;
+ }
if (clause.field != null) {
pos += clause.field.length(); // skip the field name
pos++; // skip the ':'
@@ -714,15 +750,31 @@ class ExtendedDismaxQParser extends QPar
}
if (clause != null) {
- clause.raw = s.substring(start, pos);
+ if(disallowUserField) {
+ clause.raw = clause.val;
+ } else {
+ clause.raw = s.substring(start, pos);
+ // Add default userField boost if no explicit boost exists
+ if(userFields.isAllowed(clause.field) && !clause.raw.contains("^")) {
+ Float boost = userFields.getBoost(clause.field);
+ if(boost != null)
+ clause.raw += "^" + boost;
+ }
+ }
lst.add(clause);
}
clause = new Clause();
+ disallowUserField = false;
}
return lst;
}
+ /**
+ * returns a field name or legal field alias from the current
+ * position of the string
+ * @param solrParams
+ */
public String getFieldName(String s, int pos, int end) {
if (pos >= end) return null;
int p=pos;
@@ -736,10 +788,12 @@ class ExtendedDismaxQParser extends QPar
if (!(Character.isJavaIdentifierPart(ch) || ch=='-' || ch=='.')) return null;
}
String fname = s.substring(pos, p);
- return getReq().getSchema().getFieldTypeNoEx(fname) == null ? null : fname;
+ boolean isInSchema = getReq().getSchema().getFieldTypeNoEx(fname) != null;
+ boolean isAlias = solrParams.get("f."+fname+".qf") != null;
+
+ return (isInSchema || isAlias) ? fname : null;
}
-
public static List<String> split(String s, boolean ignoreQuote) {
ArrayList<String> lst = new ArrayList<String>(4);
int pos=0, start=0, end=s.length();
@@ -851,7 +905,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected void addClause(List clauses, int conj, int mods, Query q) {
-//System.out.println("addClause:clauses="+clauses+" conj="+conj+" mods="+mods+" q="+q);
super.addClause(clauses, conj, mods, q);
}
@@ -874,6 +927,15 @@ class ExtendedDismaxQParser extends QPar
a.fields = fieldBoosts;
aliases.put(field, a);
}
+
+ /**
+ * Returns the aliases found for a field.
+ * @param field source field name
+ * @return null if there are no aliases for the field
+ */
+ public Alias getAlias(String field) {
+ return aliases.get(field);
+ }
QType type;
@@ -886,8 +948,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getFieldQuery(String field, String val, boolean quoted) throws ParseException {
-//System.out.println("getFieldQuery: val="+val);
-
this.type = QType.FIELD;
this.field = field;
this.val = val;
@@ -897,8 +957,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getFieldQuery(String field, String val, int slop) throws ParseException {
-//System.out.println("getFieldQuery: val="+val+" slop="+slop);
-
this.type = QType.PHRASE;
this.field = field;
this.val = val;
@@ -908,7 +966,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getPrefixQuery(String field, String val) throws ParseException {
-//System.out.println("getPrefixQuery: val="+val);
if (val.equals("") && field.equals("*")) {
return new MatchAllDocsQuery();
}
@@ -920,8 +977,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getRangeQuery(String field, String a, String b, boolean inclusive) throws ParseException {
-//System.out.println("getRangeQuery:");
-
this.type = QType.RANGE;
this.field = field;
this.val = a;
@@ -932,8 +987,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getWildcardQuery(String field, String val) throws ParseException {
-//System.out.println("getWildcardQuery: val="+val);
-
if (val.equals("*")) {
if (field.equals("*")) {
return new MatchAllDocsQuery();
@@ -949,8 +1002,6 @@ class ExtendedDismaxQParser extends QPar
@Override
protected Query getFuzzyQuery(String field, String val, float minSimilarity) throws ParseException {
-//System.out.println("getFuzzyQuery: val="+val);
-
this.type = QType.FUZZY;
this.field = field;
this.val = val;
@@ -965,9 +1016,9 @@ class ExtendedDismaxQParser extends QPar
* DisjunctionMaxQuery. (so yes: aliases which point at other
* aliases should work)
*/
- protected Query getAliasedQuery()
- throws ParseException {
+ protected Query getAliasedQuery() throws ParseException {
Alias a = aliases.get(field);
+ this.validateCyclicAliasing(field);
if (a != null) {
List<Query> lst = getQueries(a);
if (lst == null || lst.size()==0)
@@ -1003,15 +1054,43 @@ class ExtendedDismaxQParser extends QPar
}
}
+ /**
+ * Validate there is no cyclic referencing in the aliasing
+ */
+ private void validateCyclicAliasing(String field) throws ParseException {
+ Set<String> set = new HashSet<String>();
+ set.add(field);
+ if(validateField(field, set)) {
+ throw new ParseException("Field aliases lead to a cycle");
+ }
+ }
+
+ private boolean validateField(String field, Set<String> set) {
+ if(this.getAlias(field) == null) {
+ return false;
+ }
+ boolean hascycle = false;
+ for(String referencedField:this.getAlias(field).fields.keySet()) {
+ if(!set.add(referencedField)) {
+ hascycle = true;
+ } else {
+ if(validateField(referencedField, set)) {
+ hascycle = true;
+ }
+ set.remove(referencedField);
+ }
+ }
+ return hascycle;
+ }
- protected List<Query> getQueries(Alias a) throws ParseException {
+ protected List<Query> getQueries(Alias a) throws ParseException {
if (a == null) return null;
if (a.fields.size()==0) return null;
List<Query> lst= new ArrayList<Query>(4);
for (String f : a.fields.keySet()) {
this.field = f;
- Query sub = getQuery();
+ Query sub = getAliasedQuery();
if (sub != null) {
Float boost = a.fields.get(f);
if (boost != null) {
@@ -1064,8 +1143,131 @@ class ExtendedDismaxQParser extends QPar
if (q instanceof BooleanQuery && ((BooleanQuery)q).clauses().size()==0) return true;
return false;
}
-}
+
+ /**
+ * Class that encapsulates the input from userFields parameter and can answer whether
+ * a field allowed or disallowed as fielded query in the query string
+ */
+ static class UserFields {
+ private Map<String,Float> userFieldsMap;
+ private DynamicField[] dynamicUserFields;
+ private DynamicField[] negativeDynamicUserFields;
+
+ UserFields(Map<String,Float> ufm) {
+ userFieldsMap = ufm;
+ if (0 == userFieldsMap.size()) {
+ userFieldsMap.put("*", null);
+ }
+
+ // Process dynamic patterns in userFields
+ ArrayList<DynamicField> dynUserFields = new ArrayList<DynamicField>();
+ ArrayList<DynamicField> negDynUserFields = new ArrayList<DynamicField>();
+ for(String f : userFieldsMap.keySet()) {
+ if(f.contains("*")) {
+ if(f.startsWith("-"))
+ negDynUserFields.add(new DynamicField(f.substring(1)));
+ else
+ dynUserFields.add(new DynamicField(f));
+ }
+ }
+ Collections.sort(dynUserFields);
+ dynamicUserFields = dynUserFields.toArray(new DynamicField[dynUserFields.size()]);
+ Collections.sort(negDynUserFields);
+ negativeDynamicUserFields = negDynUserFields.toArray(new DynamicField[negDynUserFields.size()]);
+ }
+
+ /**
+ * Is the given field name allowed according to UserFields spec given in the uf parameter?
+ * @param fname the field name to examine
+ * @return true if the fielded queries are allowed on this field
+ */
+ public boolean isAllowed(String fname) {
+ boolean res = ((userFieldsMap.containsKey(fname) || isDynField(fname, false)) &&
+ !userFieldsMap.containsKey("-"+fname) &&
+ !isDynField(fname, true));
+ return res;
+ }
+
+ private boolean isDynField(String field, boolean neg) {
+ return getDynFieldForName(field, neg) == null ? false : true;
+ }
+
+ private String getDynFieldForName(String f, boolean neg) {
+ for( DynamicField df : neg?negativeDynamicUserFields:dynamicUserFields ) {
+ if( df.matches( f ) ) return df.wildcard;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the default user field boost associated with the given field.
+ * This is parsed from the uf parameter, and may be specified as wildcards, e.g. *name^2.0 or *^3.0
+ * @param field the field to find boost for
+ * @return the float boost value associated with the given field or a wildcard matching the field
+ */
+ public Float getBoost(String field) {
+ return (userFieldsMap.containsKey(field)) ?
+ userFieldsMap.get(field) : // Exact field
+ userFieldsMap.get(getDynFieldForName(field, false)); // Dynamic field
+ }
+ }
+
+ /* Represents a dynamic field, for easier matching, inspired by same class in IndexSchema */
+ static class DynamicField implements Comparable<DynamicField> {
+ final static int STARTS_WITH=1;
+ final static int ENDS_WITH=2;
+ final static int CATCHALL=3;
+
+ final String wildcard;
+ final int type;
+
+ final String str;
+ protected DynamicField(String wildcard) {
+ this.wildcard = wildcard;
+ if (wildcard.equals("*")) {
+ type=CATCHALL;
+ str=null;
+ }
+ else if (wildcard.startsWith("*")) {
+ type=ENDS_WITH;
+ str=wildcard.substring(1);
+ }
+ else if (wildcard.endsWith("*")) {
+ type=STARTS_WITH;
+ str=wildcard.substring(0,wildcard.length()-1);
+ }
+ else {
+ throw new RuntimeException("dynamic field name must start or end with *");
+ }
+ }
+
+ /*
+ * Returns true if the regex wildcard for this DynamicField would match the input field name
+ */
+ public boolean matches(String name) {
+ if (type==CATCHALL) return true;
+ else if (type==STARTS_WITH && name.startsWith(str)) return true;
+ else if (type==ENDS_WITH && name.endsWith(str)) return true;
+ else return false;
+ }
+
+ /**
+ * Sort order is based on length of regex. Longest comes first.
+ * @param other The object to compare to.
+ * @return a negative integer, zero, or a positive integer
+ * as this object is less than, equal to, or greater than
+ * the specified object.
+ */
+ public int compareTo(DynamicField other) {
+ return other.wildcard.length() - wildcard.length();
+ }
+
+ public String toString() {
+ return this.wildcard;
+ }
+ }
+}
final class ExtendedAnalyzer extends Analyzer {
final Map<String, Analyzer> map = new HashMap<String, Analyzer>();
@@ -1175,4 +1377,4 @@ final class ExtendedAnalyzer extends Ana
// TODO: done to fix stop word removal bug - could be done while still using resusable?
return tokenStream(fieldName, reader);
}
-}
+}
\ No newline at end of file
Modified: lucene/dev/branches/branch_3x/solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java?rev=1299665&r1=1299664&r2=1299665&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java (original)
+++ lucene/dev/branches/branch_3x/solr/core/src/test/org/apache/solr/search/TestExtendedDismaxParser.java Mon Mar 12 13:14:40 2012
@@ -17,6 +17,9 @@
package org.apache.solr.search;
+import java.io.IOException;
+
+import org.apache.solr.common.SolrException;
import org.apache.solr.util.AbstractSolrTestCase;
public class TestExtendedDismaxParser extends AbstractSolrTestCase {
@@ -31,16 +34,6 @@ public class TestExtendedDismaxParser ex
// if you override setUp or tearDown, you better call
// the super classes version
super.setUp();
- }
- @Override
- public void tearDown() throws Exception {
- // if you override setUp or tearDown, you better call
- // the super classes version
- super.tearDown();
- }
-
- // test the edismax query parser based on the dismax parser
- public void testFocusQueryParser() {
assertU(adoc("id", "42", "trait_ss", "Tool", "trait_ss", "Obnoxious",
"name", "Zapp Brannigan"));
assertU(adoc("id", "43" ,
@@ -63,6 +56,16 @@ public class TestExtendedDismaxParser ex
assertU(adoc("id", "50", "text_sw", "start new big city end"));
assertU(commit());
+ }
+ @Override
+ public void tearDown() throws Exception {
+ // if you override setUp or tearDown, you better call
+ // the super classes version
+ super.tearDown();
+ }
+
+ // test the edismax query parser based on the dismax parser
+ public void testFocusQueryParser() {
String allq = "id:[42 TO 50]";
String allr = "*[count(//doc)=9]";
String oner = "*[count(//doc)=1]";
@@ -239,4 +242,155 @@ public class TestExtendedDismaxParser ex
}
+ public void testUserFields() {
+ String oner = "*[count(//doc)=1]";
+ String nor = "*[count(//doc)=0]";
+
+ // User fields
+ // Default is allow all "*"
+ // If a list of fields are given, only those are allowed "foo bar"
+ // Possible to invert with "-" syntax:
+ // Disallow all: "-*"
+ // Allow all but id: "* -id"
+ // Also supports "dynamic" field name wildcarding
+ assertQ(req("defType","edismax", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","*", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","id", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","-*", "q","id:42"),
+ nor);
+
+ assertQ(req("defType","edismax", "uf","loremipsum", "q","id:42"),
+ nor);
+
+ assertQ(req("defType","edismax", "uf","* -id", "q","id:42"),
+ nor);
+
+ assertQ(req("defType","edismax", "uf","* -loremipsum", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","id^5.0", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","*^5.0", "q","id:42"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","id^5.0", "q","id:42^10.0"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","na*", "q","name:Zapp"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","*me", "q","name:Zapp"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","* -na*", "q","name:Zapp"),
+ nor);
+
+ assertQ(req("defType","edismax", "uf","*me -name", "q","name:Zapp"),
+ nor);
+
+ assertQ(req("defType","edismax", "uf","*ame -*e", "q","name:Zapp"),
+ nor);
+
+ // Boosts from user fields
+ assertQ(req("defType","edismax", "debugQuery","true", "rows","0", "q","id:42"),
+ "//str[@name='parsedquery_toString'][.='+id:42']");
+
+ assertQ(req("defType","edismax", "debugQuery","true", "rows","0", "uf","*^5.0", "q","id:42"),
+ "//str[@name='parsedquery_toString'][.='+id:42^5.0']");
+
+ assertQ(req("defType","edismax", "debugQuery","true", "rows","0", "uf","*^2.0 id^5.0 -xyz", "q","name:foo"),
+ "//str[@name='parsedquery_toString'][.='+name:foo^2.0']");
+
+ assertQ(req("defType","edismax", "debugQuery","true", "rows","0", "uf","i*^5.0", "q","id:42"),
+ "//str[@name='parsedquery_toString'][.='+id:42^5.0']");
+
+
+ assertQ(req("defType","edismax", "uf","-*", "q","cannons"),
+ oner);
+
+ assertQ(req("defType","edismax", "uf","* -id", "q","42", "qf", "id"), oner);
+
+ }
+
+ public void testAliasing() throws IOException, Exception {
+ String oner = "*[count(//doc)=1]";
+ String twor = "*[count(//doc)=2]";
+ String nor = "*[count(//doc)=0]";
+
+ // Aliasing
+ // Single field
+ assertQ(req("defType","edismax", "q","myalias:Zapp"),
+ nor);
+
+ assertQ(req("defType","edismax", "q","myalias:Zapp", "f.myalias.qf","name"),
+ oner);
+
+ // Multi field
+ assertQ(req("defType","edismax", "uf", "myalias", "q","myalias:(Zapp Obnoxious)", "f.myalias.qf","name^2.0 mytrait_ss^5.0", "mm", "50%"),
+ oner);
+
+ // Multi field
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Obnoxious", "f.myalias.qf","name^2.0 mytrait_ss^5.0"),
+ nor);
+
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Obnoxious", "qf","myalias^10.0", "f.myalias.qf","name^2.0 mytrait_ss^5.0"), oner);
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Obnoxious", "qf","myalias^10.0", "f.myalias.qf","name^2.0 trait_ss^5.0"), twor);
+ assertQ(req("defType","edismax", "q","Zapp Obnoxious", "qf","myalias^10.0", "f.myalias.qf","name^2.0 trait_ss^5.0", "mm", "100%"), oner);
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Obnoxious", "qf","who^10.0 where^3.0", "f.who.qf","name^2.0", "f.where.qf", "mytrait_ss^5.0"), oner);
+
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Obnoxious", "qf","myalias", "f.myalias.qf","name mytrait_ss", "uf", "myalias"), oner);
+
+ assertQ(req("defType","edismax", "mm", "0", "uf","who", "q","who:(Zapp Obnoxious)", "f.who.qf", "name^2.0 trait_ss^5.0", "qf", "id"), twor);
+ assertQ(req("defType","edismax", "mm", "0", "uf","* -name", "q","who:(Zapp Obnoxious)", "f.who.qf", "name^2.0 trait_ss^5.0"), twor);
+
+ }
+
+ public void testAliasingBoost() throws IOException, Exception {
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","myalias", "f.myalias.qf","name trait_ss^0.5"), "//result/doc[1]/str[@name='id']=42", "//result/doc[2]/str[@name='id']=47");//doc 42 should score higher than 46
+ assertQ(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","myalias^100 name", "f.myalias.qf","trait_ss^0.5"), "//result/doc[1]/str[@name='id']=47", "//result/doc[2]/str[@name='id']=42");//Now the order should be inverse
+ }
+
+ public void testCyclicAliasing() throws IOException, Exception {
+ try {
+ h.query(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","who", "f.who.qf","name","f.name.qf","who"));
+ fail("Simple cyclic alising");
+ } catch (SolrException e) {
+ assertTrue(e.getCause().getMessage().contains("Field aliases lead to a cycle"));
+ }
+
+ try {
+ h.query(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","who", "f.who.qf","name","f.name.qf","myalias", "f.myalias.qf","who"));
+ fail();
+ } catch (SolrException e) {
+ assertTrue(e.getCause().getMessage().contains("Field aliases lead to a cycle"));
+ }
+
+ try {
+ h.query(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","field1", "f.field1.qf","field2 field3","f.field2.qf","field4 field5", "f.field4.qf","field5", "f.field5.qf","field6", "f.field3.qf","field6"));
+ } catch (SolrException e) {
+ fail("This is not cyclic alising");
+ }
+
+ try {
+ h.query(req("defType","edismax", "mm", "0", "q","Zapp Pig", "qf","field1", "f.field1.qf","field2 field3", "f.field2.qf","field4 field5", "f.field4.qf","field5", "f.field5.qf","field4"));
+ fail();
+ } catch (SolrException e) {
+ assertTrue(e.getCause().getMessage().contains("Field aliases lead to a cycle"));
+ }
+
+ try {
+ h.query(req("defType","edismax", "mm", "0", "q","who:(Zapp Pig)", "qf","field1", "f.who.qf","name","f.name.qf","myalias", "f.myalias.qf","who"));
+ fail();
+ } catch (SolrException e) {
+ assertTrue(e.getCause().getMessage().contains("Field aliases lead to a cycle"));
+ }
+ }
+
}