SOLR-8208: [subquery] document transformer executes separate requests per result document.

This commit is contained in:
Mikhail Khludnev 2016-05-07 23:33:15 +03:00
parent 4a58b3d803
commit 7571e747c3
10 changed files with 1430 additions and 11 deletions

View File

@ -133,6 +133,8 @@ New Features
Auto Add Replica feature. For this you can set a "maxCoresPerNode" property via the Cluster Property API
(Varun Thacker, Mark Miller)
* SOLR-8208: [subquery] document transformer executes separate requests per result document. (Cao Manh Dat via Mikhail Khludnev)
Bug Fixes
----------------------

View File

@ -74,16 +74,17 @@ public class MoreLikeThisComponent extends SearchComponent {
public void process(ResponseBuilder rb) throws IOException {
SolrParams params = rb.req.getParams();
ReturnFields returnFields = new SolrReturnFields( rb.req );
int flags = 0;
if (returnFields.wantsScore()) {
flags |= SolrIndexSearcher.GET_SCORES;
}
rb.setFieldFlags(flags);
if (params.getBool(MoreLikeThisParams.MLT, false)) {
ReturnFields returnFields = new SolrReturnFields( rb.req );
int flags = 0;
if (returnFields.wantsScore()) {
flags |= SolrIndexSearcher.GET_SCORES;
}
rb.setFieldFlags(flags);
log.debug("Starting MoreLikeThis.Process. isShard: "
+ params.getBool(ShardParams.IS_SHARD));
SolrIndexSearcher searcher = rb.req.getSearcher();

View File

@ -0,0 +1,360 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response.transform;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.ResultContext;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.DocList;
import org.apache.solr.search.DocSlice;
import org.apache.solr.search.JoinQParserPlugin;
import org.apache.solr.search.ReturnFields;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SolrReturnFields;
import org.apache.solr.search.TermsQParserPlugin;
/**
*
* This transformer executes subquery per every result document. It must be be given uniq name.
* There might be a few of them, eg <code>fl=*,foo:[subquery],bar:[subquery]</code>.
* Every [subquery] occurrence adds a field into a result document with the given name,
* the value of this field is a document list, which is a result of executing subquery using
* document fields as an input.
*
* <h3>Subquery Parameters Shift</h3>
* if subquery is declared as <code>fl=*,foo:[subquery]</code>, subquery parameters
* are prefixed with the given name and period. eg <br>
* <code>q=*:*&amp;fl=*,foo:[subquery]&amp;foo.q=to be continued&amp;foo.rows=10&amp;foo.sort=id desc</code>
*
* <h3>Document field as an input param for subquery</h3>
*
* It's necessary to pass some document field value as a parametr for subquery. It's supported via
* implicit <code>row.<i>fieldname</i></code> parameter, and can be (but might not only) referred via
* Local Parameters syntax.<br>
* <code>q=namne:john&amp;fl=name,id,depts:[subquery]&amp;depts.q={!terms f=id v=$row.dept_id}&amp;depts.rows=10</code>
* Here departmens are retrieved per every employee in search result. We can say that it's like SQL
* <code> join ON emp.dept_id=dept.id </code><br>
* Note, when document field has multiple values they are concatenated with comma by default, it can be changed by
* <code>foo:[subquery separator=' ']</code> local parameter, this mimics {@link TermsQParserPlugin} to work smoothly with.
*
* <h3>Cores and Collections in Cloud</h3>
* use <code>foo:[subquery fromIndex=departments]</code> invoke subquery on another core on this node, it's like
* {@link JoinQParserPlugin} for non SolrCloud mode. <b>But for SolrCloud</b> just (and only) <b>explicitly specify</b>
* its' native parameters like <code>collection, shards</code> for subquery, eg<br>
* <code>q=*:*&amp;fl=*,foo:[subquery]&amp;foo.q=cloud&amp;foo.collection=departments</code>
*
*/
public class SubQueryAugmenterFactory extends TransformerFactory{
@Override
public DocTransformer create(String field, SolrParams params, SolrQueryRequest req) {
if (field.contains("[") || field.contains("]")) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"please give an exlicit name for [subquery] column ie fl=relation:[subquery ..]");
}
checkThereIsNoDupe(field, req.getContext());
String fromIndex = params.get("fromIndex");
final SolrClient solrClient;
solrClient = new EmbeddedSolrServer(req.getCore());
SolrParams subParams = retainAndShiftPrefix(req.getParams(), field+".");
return new SubQueryAugmenter(solrClient, fromIndex, field,
field,
subParams,
params.get(TermsQParserPlugin.SEPARATOR, ","));
}
@SuppressWarnings("unchecked")
private void checkThereIsNoDupe(String field, Map<Object,Object> context) {
// find a map
final Map conflictMap;
final String conflictMapKey = getClass().getSimpleName();
if (context.containsKey(conflictMapKey)) {
conflictMap = (Map) context.get(conflictMapKey);
} else {
conflictMap = new HashMap<>();
context.put(conflictMapKey, conflictMap);
}
// check entry absence
if (conflictMap.containsKey(field)) {
throw new SolrException(ErrorCode.BAD_REQUEST,
"[subquery] name "+field+" is duplicated");
} else {
conflictMap.put(field, true);
}
}
private SolrParams retainAndShiftPrefix(SolrParams params, String subPrefix) {
ModifiableSolrParams out = new ModifiableSolrParams();
Iterator<String> baseKeyIt = params.getParameterNamesIterator();
while (baseKeyIt.hasNext()) {
String key = baseKeyIt.next();
if (key.startsWith(subPrefix)) {
out.set(key.substring(subPrefix.length()), params.getParams(key));
}
}
return out;
}
}
class SubQueryAugmenter extends DocTransformer {
private static final class Result extends ResultContext {
private final SolrDocumentList docList;
final SolrReturnFields justWantAllFields = new SolrReturnFields();
private Result(SolrDocumentList docList) {
this.docList = docList;
}
@Override
public ReturnFields getReturnFields() {
return justWantAllFields;
}
@Override
public Iterator<SolrDocument> getProcessedDocuments(){
return docList.iterator();
}
@Override
public boolean wantsScores() {
return justWantAllFields.wantsScore();
}
@Override
public DocList getDocList() {
return new DocSlice((int)docList.getStart(),
docList.size(), new int[0], new float[docList.size()],
(int) docList.getNumFound(),
docList.getMaxScore() == null ? Float.NaN : docList.getMaxScore());
}
@Override
public SolrIndexSearcher getSearcher() {
return null;
}
@Override
public SolrQueryRequest getRequest() {
return null;
}
@Override
public Query getQuery() {
return null;
}
}
/** project document values to prefixed parameters
* multivalues are joined with a separator, it always return single value */
static final class DocRowParams extends SolrParams {
final private SolrDocument doc;
final private String prefixDotRowDot;
final private String separator;
public DocRowParams(SolrDocument doc, String prefix, String separator ) {
this.doc = doc;
this.prefixDotRowDot = "row.";//prefix+ ".row.";
this.separator = separator;
}
@Override
public String[] getParams(String param) {
final Collection<Object> vals = mapToDocField(param);
if (vals != null) {
StringBuilder rez = new StringBuilder();
int i = 0;
for (Iterator iterator = vals.iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
rez.append(convertFieldValue(object));
if (iterator.hasNext()) {
rez.append(separator);
}
}
return new String[]{rez.toString()};
}
return null;
}
@Override
public String get(String param) {
final String[] aVal = this.getParams(param);
if (aVal != null) {
assert aVal.length == 1 : "that's how getParams is written" ;
return aVal[0];
}
return null;
}
/** @return null if prefix doesn't match, field is absent or empty */
protected Collection<Object> mapToDocField(String param) {
if (param.startsWith(prefixDotRowDot)) {
final String docFieldName = param.substring(prefixDotRowDot.length());
final Collection<Object> vals = doc.getFieldValues(docFieldName);
if (vals == null || vals.isEmpty()) {
return null;
} else {
return vals;
}
}
return null;
}
protected String convertFieldValue(Object val) {
if (val instanceof IndexableField) {
IndexableField f = (IndexableField)val;
return f.stringValue();
}
return val.toString();
}
@Override
public Iterator<String> getParameterNamesIterator() {
final Iterator<String> fieldNames = doc.getFieldNames().iterator();
return new Iterator<String>() {
@Override
public boolean hasNext() {
return fieldNames.hasNext();
}
@Override
public String next() {
final String fieldName = fieldNames.next();
return prefixDotRowDot + fieldName;
}
};
}
}
final private String name;
final private SolrParams baseSubParams;
final private String prefix;
final private String separator;
final private SolrClient server;
final private String coreName;
public SubQueryAugmenter(SolrClient server, String coreName,
String name,String prefix, SolrParams baseSubParams, String separator) {
this.name = name;
this.prefix = prefix;
this.baseSubParams = baseSubParams;
this.separator = separator;
this.server = server;
this.coreName = coreName;
}
@Override
public String getName() {
return name;
}
@Override
public void transform(SolrDocument doc, int docid, float score) {
final SolrParams docWithDeprefixed = SolrParams.wrapDefaults(
new DocRowParams(doc, prefix, separator), baseSubParams);
try {
Callable<QueryResponse> subQuery = new Callable<QueryResponse>() {
@Override
public QueryResponse call() throws Exception {
try {
return new QueryResponse(
server.request(
new QueryRequest(docWithDeprefixed), coreName)
, server);
} finally {
}
}
};
QueryResponse response =
SolrRequestInfoSuspender.doInSuspension(subQuery);
final SolrDocumentList docList = (SolrDocumentList) response.getResults();
doc.setField(getName(), new Result(docList));
} catch (Exception e) {
String docString = doc.toString();
throw new SolrException(ErrorCode.BAD_REQUEST, "while invoking " +
name + ":[subquery"+ (coreName!=null ? "fromIndex="+coreName : "") +"] on doc=" +
docString.substring(0, Math.min(100, docString.length())), e.getCause());
} finally {}
}
// look ma!! no hands..
final static class SolrRequestInfoSuspender extends SolrRequestInfo {
private SolrRequestInfoSuspender(SolrQueryRequest req, SolrQueryResponse rsp) {
super(req, rsp);
}
/** Suspends current SolrRequestInfo invoke the given action, and resumes then */
static <T> T doInSuspension(Callable<T> action) throws Exception {
final SolrRequestInfo info = threadLocal.get();
try {
threadLocal.remove();
return action.call();
} finally {
setRequestInfo(info);
}
}
}
}

View File

@ -47,6 +47,7 @@ public abstract class TransformerFactory implements NamedListInitializedPlugin
defaultFactories.put( "docid", new DocIdAugmenterFactory() );
defaultFactories.put( "shard", new ShardAugmenterFactory() );
defaultFactories.put( "child", new ChildDocTransformerFactory() );
defaultFactories.put( "subquery", new SubQueryAugmenterFactory() );
defaultFactories.put( "json", new RawValueTransformerFactory("json") );
defaultFactories.put( "xml", new RawValueTransformerFactory("xml") );
defaultFactories.put( "geo", new GeoTransformerFactory() );

View File

@ -87,6 +87,10 @@
<dynamicField name="*_dt_dv" type="date" indexed="true" stored="false" docValues="true"/>
<dynamicField name="*_dts_dv" type="date" indexed="true" stored="false" docValues="true" multiValued="true"/>
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/>
</fields>
<defaultSearchField>id</defaultSearchField>

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response;
package org.apache.solr.response.transform;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
@ -47,6 +47,8 @@ public class TestChildDocTransformer extends SolrTestCaseJ4 {
createIndex(titleVals);
testParentFilterJSON();
testParentFilterXML();
testSubQueryParentFilterJSON();
testSubQueryParentFilterXML();
}
@Test
@ -54,6 +56,9 @@ public class TestChildDocTransformer extends SolrTestCaseJ4 {
createSimpleIndex();
testChildDoctransformerJSON();
testChildDoctransformerXML();
testSubQueryXML();
testSubQueryJSON();
}
private void testChildDoctransformerXML() {
@ -88,6 +93,96 @@ public class TestChildDocTransformer extends SolrTestCaseJ4 {
assertQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "subject,[child parentFilter=\"subject:parentDocument\" childFilter=\"title:bar\" limit=2]"), test3);
}
private void testSubQueryXML() {
String test1[];
{
final String subqueryPath = "/result[@name='children'][@numFound='6']";
test1 = new String[] {
"//*[@numFound='1']",
"/response/result/doc[1]" + subqueryPath + "/doc[1]/int[@name='id']='2'" ,
"/response/result/doc[1]" + subqueryPath + "/doc[2]/int[@name='id']='3'" ,
"/response/result/doc[1]" + subqueryPath + "/doc[3]/int[@name='id']='4'" ,
"/response/result/doc[1]" + subqueryPath + "/doc[4]/int[@name='id']='5'" ,
"/response/result/doc[1]" + subqueryPath + "/doc[5]/int[@name='id']='6'" ,
"/response/result/doc[1]" + subqueryPath + "/doc[6]/int[@name='id']='7'"};
}
assertQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","10"),
test1);
String test2[] = new String[] {
"//*[@numFound='1']",
"/response/result/doc[1]/result[@name='children'][@numFound='3']/doc[1]/int[@name='id']='2'" ,
"/response/result/doc[1]/result[@name='children'][@numFound='3']/doc[2]/int[@name='id']='4'" ,
"/response/result/doc[1]/result[@name='children'][@numFound='3']/doc[3]/int[@name='id']='6'" };
assertQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","10",
"children.fq","title:foo"
), test2);
String test3[] = new String[] {
"//*[@numFound='1']",
"/response/result/doc[1]/result[@name='children'][@numFound='3']/doc[1]/int[@name='id']='3'" ,
"/response/result/doc[1]/result[@name='children'][@numFound='3']/doc[2]/int[@name='id']='5'" };
assertQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","2",
"children.fq","title:bar",
"children.sort","_docid_ asc"
), test3);
}
private void testSubQueryJSON() throws Exception {
String[] test1 = new String[] {
"/response/docs/[0]/children/docs/[0]/id==2",
"/response/docs/[0]/children/docs/[1]/id==3",
"/response/docs/[0]/children/docs/[2]/id==4",
"/response/docs/[0]/children/docs/[3]/id==5",
"/response/docs/[0]/children/docs/[4]/id==6",
"/response/docs/[0]/children/docs/[5]/id==7"
};
String[] test2 = new String[] {
"/response/docs/[0]/children/docs/[0]/id==2",
"/response/docs/[0]/children/docs/[1]/id==4",
"/response/docs/[0]/children/docs/[2]/id==6"
};
String[] test3 = new String[] {
"/response/docs/[0]/children/docs/[0]/id==3",
"/response/docs/[0]/children/docs/[1]/id==5"
};
assertJQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","10"), test1);
assertJQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","10",
"children.fq","title:foo"), test2);
assertJQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.rows","2",
"children.fq","title:bar",
"children.sort","_docid_ asc"), test3);
}
private void testChildDoctransformerJSON() throws Exception {
String[] test1 = new String[] {
@ -214,6 +309,38 @@ public class TestChildDocTransformer extends SolrTestCaseJ4 {
tests);
}
private void testSubQueryParentFilterJSON() throws Exception {
String[] tests = new String[] {
"/response/docs/[0]/id==1",
"/response/docs/[0]/children/docs/[0]/id==2",
"/response/docs/[0]/children/docs/[0]/cat/[0]/=='childDocument'",
"/response/docs/[0]/children/docs/[0]/title/[0]/=='" + titleVals[0] + "'",
"/response/docs/[1]/id==4",
"/response/docs/[1]/children/docs/[0]/id==5",
"/response/docs/[1]/children/docs/[0]/cat/[0]/=='childDocument'",
"/response/docs/[1]/children/docs/[0]/title/[0]/=='" + titleVals[1] + "'"
};
assertJQ(req(
"q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"sort", "id asc",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.fq","cat:childDocument",
"children.sort","_docid_ asc"),
tests);
assertJQ(req(
"q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "id,children:[subquery]",
"sort", "id asc",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.fq","cat:childDocument",
"children.sort","_docid_ asc"),
tests);
}
private void testParentFilterXML() {
@ -241,4 +368,36 @@ public class TestChildDocTransformer extends SolrTestCaseJ4 {
tests);
}
private void testSubQueryParentFilterXML() {
String tests[] = new String[] {
"//*[@numFound='2']",
"/response/result/doc[1]/int[@name='id']='1'" ,
"/response/result/doc[1]/result[@name='children'][@numFound=1]/doc[1]/int[@name='id']='2'" ,
"/response/result/doc[1]/result[@name='children'][@numFound=1]/doc[1]/arr[@name='cat']/str[1]='childDocument'" ,
"/response/result/doc[1]/result[@name='children'][@numFound=1]/doc[1]/arr[@name='title']/str[1]='" + titleVals[0] + "'" ,
"/response/result/doc[2]/int[@name='id']='4'" ,
"/response/result/doc[2]/result[@name='children'][@numFound=1]/doc[1]/int[@name='id']='5'",
"/response/result/doc[2]/result[@name='children'][@numFound=1]/doc[1]/arr[@name='cat']/str[1]='childDocument'",
"/response/result/doc[2]/result[@name='children'][@numFound=1]/doc[1]/arr[@name='title']/str[1]='" + titleVals[1] + "'"};
assertQ(req(
"q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "*,children:[subquery]",
"sort", "id asc",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.fq","cat:childDocument",
"children.sort","_docid_ asc"
),
tests);
assertQ(req("q", "*:*", "fq", "subject:\"parentDocument\" ",
"fl", "id,children:[subquery]",
"sort", "id asc",
"children.q","{!child of=subject:parentDocument}{!terms f=id v=$row.id}",
"children.fq","cat:childDocument",
"children.sort","_docid_ asc"),
tests);
}
}

View File

@ -0,0 +1,571 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response.transform;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import org.apache.commons.io.output.ByteArrayOutputStream;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.JavaBinCodec;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.BinaryQueryResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestSubQueryTransformer extends SolrTestCaseJ4 {
private static int peopleMultiplier;
private static int deptMultiplier;
@BeforeClass
public static void beforeTests() throws Exception {
System.setProperty("enable.update.log", "false");
initCore("solrconfig-basic.xml", "schema-docValuesJoin.xml");
peopleMultiplier = atLeast(1);
deptMultiplier = atLeast(1);
int id=0;
for (int p=0; p < peopleMultiplier; p++){
assertU(add(doc("id", ""+id++,"name_s", "john", "title_s", "Director",
"dept_ss_dv","Engineering",
"dept_i", "0",
"dept_is", "0")));
assertU(add(doc("id", ""+id++,"name_s", "mark", "title_s", "VP",
"dept_ss_dv","Marketing",
"dept_i", "1",
"dept_is", "1")));
assertU(add(doc("id", ""+id++,"name_s", "nancy", "title_s", "MTS",
"dept_ss_dv","Sales",
"dept_i", "2",
"dept_is", "2")));
assertU(add(doc("id", ""+id++,"name_s", "dave", "title_s", "MTS",
"dept_ss_dv","Support", "dept_ss_dv","Engineering",
"dept_i", "3",
"dept_is", "3", "dept_is", "0")));
assertU(add(doc("id", ""+id++,"name_s", "tina", "title_s", "VP",
"dept_ss_dv","Engineering",
"dept_i", "0",
"dept_is", "0")));
if (rarely()) {
assertU(commit("softCommit", "true"));
}
}
for (int d=0; d < deptMultiplier; d++){
assertU(add(doc("id",""+id, "id_i",""+id++,
"dept_id_s", "Engineering", "text_t","These guys develop stuff", "salary_i_dv", "1000",
"dept_id_i", "0")));
assertU(add(doc("id",""+id++,"id_i",""+id++,
"dept_id_s", "Marketing", "text_t","These guys make you look good","salary_i_dv", "1500",
"dept_id_i", "1")));
assertU(add(doc("id",""+id, "id_i",""+id++,
"dept_id_s", "Sales", "text_t","These guys sell stuff","salary_i_dv", "1600",
"dept_id_i", "2")));
assertU(add(doc("id",""+id,"id_i",""+id++,
"dept_id_s", "Support", "text_t","These guys help customers","salary_i_dv", "800",
"dept_id_i", "3")));
if (rarely()) {
assertU(commit("softCommit", "true"));
}
}
assertU(commit());
}
@Test
public void testJohnOrNancySingleField() throws Exception {
//System.out.println("p "+peopleMultiplier+" d "+deptMultiplier);
assertQ("subq1.fl is limited to single field",
req("q","name_s:(john nancy)", "indent","true",
"fl","name_s_dv,depts:[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"depts.fl","text_t",
"depts.indent","true",
"depts.rows",""+deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts'][@numFound='" +
deptMultiplier+ "']/doc/str[@name='text_t'][.='These guys develop stuff'])="+
(peopleMultiplier * deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='nancy']/../result[@name='depts'][@numFound='" +
deptMultiplier+ "']/doc/str[@name='text_t'][.='These guys sell stuff'])="+
(peopleMultiplier * deptMultiplier),
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)[1]/result[@name='depts']/doc[1]/*)=1",
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)[1]/result[@name='depts']/doc["+ deptMultiplier+ "]/*)=1",
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)["+ peopleMultiplier +"]/result[@name='depts'][@numFound='" +
deptMultiplier+ "']/doc[1]/*)=1",
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)["+ peopleMultiplier +"]/result[@name='depts'][@numFound='" +
deptMultiplier+ "']/doc["+ deptMultiplier+ "]/*)=1"
);
}
final String[] johnAndNancyParams = new String[]{"q","name_s:(john nancy)", "indent","true",
"fl","name_s_dv,depts:[subquery]",
"fl","depts_i:[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"depts.fl","text_t",
"depts.indent","true",
"depts.rows",""+deptMultiplier,
"depts_i.q","{!term f=dept_id_i v=$row.dept_i_dv}",
"depts_i.fl","text_t", // multi val subquery param check
"depts_i.fl","dept_id_s_dv",
"depts_i.indent","true",
"depts_i.rows",""+deptMultiplier};
@Test
public void testTwoSubQueriesAndByNumberWithTwoFields() throws Exception {
final SolrQueryRequest johnOrNancyTwoFL = req(johnAndNancyParams);
assertQ("call subquery twice a row, once by number, with two fls via multival params",
johnOrNancyTwoFL,
"count(//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts']/doc/str[@name='text_t'][.='These guys develop stuff'])="+
(peopleMultiplier * deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts_i']/doc/str[@name='dept_id_s_dv'][.='Engineering'])="+
(peopleMultiplier * deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='nancy']/../result[@name='depts_i']/doc/str[@name='text_t'][.='These guys sell stuff'])="+
(peopleMultiplier * deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='nancy']/../result[@name='depts_i']/doc/str[@name='dept_id_s_dv'][.='Sales'])="+
(peopleMultiplier * deptMultiplier),
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)["+ peopleMultiplier +"]/result[@name='depts_i']/doc["+ deptMultiplier+ "]/str[@name='dept_id_s_dv'][.='Engineering'])=1",
"count((//result/doc/str[@name='name_s_dv'][.='john']/..)["+ peopleMultiplier +"]/result[@name='depts_i']/doc["+ deptMultiplier+ "]/str[@name='text_t'][.='These guys develop stuff'])=1"
);
}
@Test
public void testRowsStartForSubqueryAndScores() throws Exception {
String johnDeptsIds = h.query(req(new String[]{"q","{!join from=dept_ss_dv to=dept_id_s}name_s:john",
"wt","csv",
"csv.header","false",
"fl","id",
"rows",""+deptMultiplier,
"sort", "id_i desc"
}));
ArrayList<Object> deptIds = Collections.list(
new StringTokenizer( johnDeptsIds));
final int a = random().nextInt(deptMultiplier+1);
final int b = random().nextInt(deptMultiplier+1);
final int start = Math.min(a, b) ;
final int toIndex = Math.max(a, b) ;
List<Object> expectIds = deptIds.subList(start , toIndex);
ArrayList<String> assertions = new ArrayList<>();
// count((//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts'])[1]/doc/str[@name='id'])
// random().nextInt(peopleMultiplier);
assertions.add("count((//result/doc/str[@name='name_s_dv'][.='john']/.."
+ "/result[@name='depts'][@numFound='"+deptMultiplier+"'][@start='"+start+"'])["+
(random().nextInt(peopleMultiplier)+1)
+"]/doc/str[@name='id'])=" +(toIndex-start));
// System.out.println(expectIds);
for (int i=0; i< expectIds.size(); i++) {
// (//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts'])[1]/doc[1]/str[@name='id']='15'
String ithDoc = "(//result/doc/str[@name='name_s_dv'][.='john']/.."
+ "/result[@name='depts'][@numFound='"+deptMultiplier+"'][@start='"+start+"'])["+
(random().nextInt(peopleMultiplier)+1) +
"]/doc[" +(i+1)+ "]";
assertions.add(ithDoc+"/str[@name='id'][.='"+expectIds.get(i)+"']");
// let's test scores right there
assertions.add(ithDoc+"/float[@name='score'][.='"+expectIds.get(i)+".0']");
}
String[] john = new String[]{"q","name_s:john", "indent","true",
"fl","name_s_dv,depts:[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","+{!term f=dept_id_s v=$row.dept_ss_dv}^=0 _val_:id_i",
"depts.fl","id",
"depts.fl","score",
"depts.indent","true",
"depts.rows",""+(toIndex-start),
"depts.start",""+start};
assertQ(req(john), assertions.toArray(new String[]{}));
}
@Test
public void testThreeLevel() throws Exception {
List<String> asserts = new ArrayList<>();
// dave works in both dept, get his coworkers from both
for (String dept : new String[] {"Engineering", "Support"}) { //dept_id_s_dv">Engineering
ArrayList<Object> deptWorkers = Collections.list(
new StringTokenizer( h.query(req(
"q","dept_ss_dv:"+dept ,//dept_id_i_dv
"wt","csv",
"csv.header","false",
"fl","name_s_dv",
"rows",""+peopleMultiplier*3, // dave has three coworkers in two depts
"sort", "id desc"
))));
// System.out.println(deptWorkers);
// looping dave clones
for (int p : new int []{1, peopleMultiplier}) {
// looping dept clones
for (int d : new int []{1, deptMultiplier}) {
// looping coworkers
int wPos = 1;
for (Object mate : deptWorkers) {
// (/response/result/doc/str[@name='name_s_dv'][.='dave']/..)[1]
// /result[@name='subq1']/doc/str[@name='dept_id_s_dv'][.='Engineering']/..
// /result[@name='neighbours']/doc/str[@name='name_s_dv'][.='tina']
asserts.add("((/response/result/doc/str[@name='name_s_dv'][.='dave']/..)["+p+"]"+
"/result[@name='subq1']/doc/str[@name='dept_id_s_dv'][.='"+dept+"']/..)["+ d +"]"+
"/result[@name='neighbours']/doc[" + wPos + "]/str[@name='name_s_dv'][.='"+ mate+"']");
wPos ++;
}
}
}
}
//System.out.println(asserts);
assertQ("dave works at both dept with other folks",
// System.out.println(h.query(
req(new String[]{"q","name_s:dave", "indent","true",
"fl","name_s_dv,subq1:[subquery]",
"rows","" + peopleMultiplier,
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv}",
"subq1.fl","text_t,dept_id_s_dv,neighbours:[subquery]",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2),
"subq1.neighbours.q",//flipping via numbers
random().nextBoolean() ?
"{!terms f=dept_ss_dv v=$row.dept_id_s_dv}"
: "{!terms f=dept_is v=$row.dept_id_i_dv}",
"subq1.neighbours.fl", "name_s_dv" ,
"subq1.neighbours.rows", ""+peopleMultiplier*3},
"subq1.neighbours.sort", "id desc")//,
,asserts.toArray(new String[]{})
// )
);
}
@Test
public void testNoExplicitName() throws Exception {
String[] john = new String[]{"q","name_s:john", "indent","true",
"fl","name_s_dv,"
+ "[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","+{!term f=dept_id_s v=$row.dept_ss_dv}^=0 _val_:id_i",
"depts.fl","id",
"depts.fl","score",
"depts.indent","true",
"depts.rows",""+deptMultiplier,
"depts.start","0"};
assertQEx("no prefix, no subquery", req(john), ErrorCode.BAD_REQUEST);
assertQEx("no prefix, no subsubquery",
req("q","name_s:john", "indent","true",
"fl","name_s_dv,"
+ "depts:[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","+{!term f=dept_id_s v=$row.dept_ss_dv}^=0 _val_:id_i",
"depts.fl","id",
"depts.fl","score",
"depts.fl","[subquery]",// <- here is a trouble
"depts.indent","true",
"depts.rows",""+deptMultiplier,
"depts.start","0"), ErrorCode.BAD_REQUEST);
}
@Test
public void testDupePrefix() throws Exception {
assertQEx("subquery name clash", req(new String[]{"q","name_s:(john nancy)", "indent","true",
"fl","name_s_dv,depts:[subquery]",
"fl","depts:[subquery]",
"rows","" + (2 * peopleMultiplier),
"depts.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"depts.fl","text_t",
"depts.indent","true",
"depts.rows",""+deptMultiplier,
"depts_i.q","{!term f=dept_id_i v=$depts_i.row.dept_i_dv}",
"depts_i.fl","text_t", // multi val subquery param check
"depts_i.fl","dept_id_s_dv",
"depts_i.indent","true",
"depts_i.rows",""+deptMultiplier}
), ErrorCode.BAD_REQUEST);
}
@Test
public void testJustJohnJson() throws Exception {
final SolrQueryRequest johnTwoFL = req(johnAndNancyParams);
ModifiableSolrParams params = new ModifiableSolrParams(johnTwoFL.getParams());
params.set("q","name_s:john");
johnTwoFL.setParams(params);
assertJQ(johnTwoFL,
"/response/docs/[0]/depts/docs/[0]=={text_t:\"These guys develop stuff\"}",
"/response/docs/[" + (peopleMultiplier-1) + "]/depts/docs/[" + (deptMultiplier-1) + "]=={text_t:\"These guys develop stuff\"}",
"/response/docs/[0]/depts_i/docs/[0]=={dept_id_s_dv:\"Engineering\", text_t:\"These guys develop stuff\"}",// seem like key order doesn't matter , well
"/response/docs/[" + (peopleMultiplier-1) + "]/depts_i/docs/[" + (deptMultiplier-1) + "]=="
+ "{text_t:\"These guys develop stuff\", dept_id_s_dv:\"Engineering\"}");
}
@SuppressWarnings("unchecked")
@Test
public void testJustJohnJavabin() throws Exception {
final SolrQueryRequest johnTwoFL = req(johnAndNancyParams);
ModifiableSolrParams params = new ModifiableSolrParams(johnTwoFL.getParams());
params.set("q","name_s:john");
params.set("wt","javabin");
johnTwoFL.setParams(params);
final NamedList<Object> unmarshalled;
{
SolrCore core = johnTwoFL.getCore();
SolrQueryResponse rsp = new SolrQueryResponse();
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(johnTwoFL, rsp));
SolrQueryResponse response = h.queryAndResponse(
johnTwoFL.getParams().get(CommonParams.QT), johnTwoFL);
BinaryQueryResponseWriter responseWriter = (BinaryQueryResponseWriter) core.getQueryResponseWriter(johnTwoFL);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
responseWriter.write(bytes,johnTwoFL,response);
unmarshalled = (NamedList<Object>) new JavaBinCodec().unmarshal(
new ByteArrayInputStream(bytes.toByteArray()));
johnTwoFL.close();
SolrRequestInfo.clearRequestInfo();
}
SolrDocumentList resultDocs = (SolrDocumentList)(unmarshalled.get("response"));
{
Map<String,String> engText = new HashMap<>();
engText.put("text_t", "These guys develop stuff");
Map<String,String> engId = new HashMap<>();
engId.put("text_t", "These guys develop stuff");
engId.put("dept_id_s_dv", "Engineering");
for (int docNum : new int []{0, peopleMultiplier-1}) {
SolrDocument employeeDoc = resultDocs.get(docNum);
assertEquals("john", employeeDoc.getFieldValue("name_s_dv"));
for (String subResult : new String []{"depts", "depts_i"}) {
SolrDocumentList subDoc = (SolrDocumentList)employeeDoc.getFieldValue(subResult);
for (int deptNum : new int []{0, deptMultiplier-1}) {
SolrDocument deptDoc = subDoc.get(deptNum);
Object expectedDept = (subResult.equals("depts") ? engText : engId);
assertTrue( "" + expectedDept + " equals to " + deptDoc,
expectedDept.equals(deptDoc));
}
}
}
}
}
@Test
public void testExceptionPropagation() throws Exception {
final SolrQueryRequest r = req("q","name_s:dave", "indent","true",
"fl","depts:[subquery]",
"rows","" + ( peopleMultiplier),
"depts.q","{!lucene}(",
"depts.fl","text_t",
"depts.indent","true",
"depts.rows",""+(deptMultiplier*2),
"depts.logParamsList","q,fl,rows,subq1.row.dept_ss_dv");
// System.out.println(h.query(r));
assertQEx("wrong subquery",
r,
ErrorCode.BAD_REQUEST);
assertQEx( "", req("q","name_s:dave", "indent","true",
"fl","depts:[subquery]",
"rows","1",
"depts.q","{!lucene}",
"depts.fl","text_t",
"depts.indent","true",
"depts.rows","NAN",
"depts.logParamsList","q,fl,rows,subq1.row.dept_ss_dv"),
ErrorCode.BAD_REQUEST);
}
@Test
public void testMultiValue() throws Exception {
String [] happyPathAsserts = new String[]{
"count(//result/doc/str[@name='name_s_dv'][.='dave']/../result[@name='subq1']/doc/str[@name='text_t'][.='These guys develop stuff'])="+
(peopleMultiplier * deptMultiplier),
"count(//result/doc/str[@name='name_s_dv'][.='dave']/../result[@name='subq1']/doc/str[@name='text_t'][.='These guys help customers'])="+
(peopleMultiplier * deptMultiplier),
"//result[@numFound="+peopleMultiplier+"]"};
Random random1 = random();
assertQ("dave works at both, whether we set a default separator or both",
req(new String[]{"q","name_s:dave", "indent","true",
"fl",(random().nextBoolean() ? "name_s_dv" : "*")+ //"dept_ss_dv,
",subq1:[subquery "
+((random1.nextBoolean() ? "" : "separator=,"))+"]",
"rows","" + peopleMultiplier,
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv "+((random1.nextBoolean() ? "" : "separator=,"))+"}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2),
"subq1.logParamsList","q,fl,rows,row.dept_ss_dv"}),
happyPathAsserts
);
assertQ("even via numbers",
req("q","name_s:dave", "indent","true",
"fl","dept_is_dv,name_s_dv,subq1:[subquery]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!terms f=dept_id_i v=$row.dept_is_dv}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
happyPathAsserts
);
assertQ("even if we set a separator both",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,name_s_dv,subq1:[subquery separator=\" \"]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv separator=\" \"}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
happyPathAsserts
);
String [] noMatchAtSubQ = new String[] {
"count(//result/doc/str[@name='name_s_dv'][.='dave']/../result[@name='subq1'][@numFound=0])="+
(peopleMultiplier),
"//result[@numFound="+peopleMultiplier+"]" };
assertQ("different separators, no match",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,subq1:[subquery]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv separator=\" \"}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
noMatchAtSubQ
);
assertQ("and no matter where",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,subq1:[subquery separator=\" \"]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
noMatchAtSubQ
);
assertQ("setting a wrong parser gets you nowhere",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,subq1:[subquery]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
noMatchAtSubQ
);
assertQ("but it luckily works with default query parser, but it's not really reliable",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,subq1:[subquery separator=\" \"]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!lucene df=dept_id_s v=$row.dept_ss_dv}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
happyPathAsserts
);
assertQ("even lucene qp can't help at any separator but space",
req("q","name_s:dave", "indent","true",
"fl","dept_ss_dv,name_s_dv,"
+ "subq1:[subquery "+(random().nextBoolean() ? "" : "separator=" +((random().nextBoolean() ? "" : ",")))+"]",
"rows","" + ( peopleMultiplier),
"subq1.q","{!lucene df=dept_id_s v=$row.dept_ss_dv}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMultiplier*2)),
noMatchAtSubQ
);
}
static String[] daveMultiValueSearchParams(Random random, int peopleMult, int deptMult) {
return new String[]{"q","name_s:dave", "indent","true",
"fl",(random().nextBoolean() ? "name_s_dv" : "*")+ //"dept_ss_dv,
",subq1:[subquery "
+((random.nextBoolean() ? "" : "separator=,"))+"]",
"rows","" + peopleMult,
"subq1.q","{!terms f=dept_id_s v=$row.dept_ss_dv "+((random.nextBoolean() ? "" : "separator=,"))+"}",
"subq1.fl","text_t",
"subq1.indent","true",
"subq1.rows",""+(deptMult*2),
"subq1.logParamsList","q,fl,rows,row.dept_ss_dv"};
}
}

View File

@ -0,0 +1,131 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response.transform;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.servlet.DirectSolrConnection;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
public class TestSubQueryTransformerCrossCore extends SolrTestCaseJ4 {
private static SolrCore fromCore;
@BeforeClass
public static void beforeTests() throws Exception {
System.setProperty("enable.update.log", "false"); // schema12 doesn't support _version_
initCore("solrconfig-basic.xml","schema-docValuesJoin.xml");
final CoreContainer coreContainer = h.getCoreContainer();
fromCore = coreContainer.create("fromCore", //FileSystems.getDefault().getPath( TEST_HOME()), ImmutableMap.of("config","solrconfig-basic.xml","schema","schema-docValuesJoin.xml"
ImmutableMap.of("configSet", "minimal")
);
assertU(add(doc("id", "1","name_s", "john", "title_s", "Director", "dept_ss_dv","Engineering",
"text_t","These guys develop stuff")));
assertU(add(doc("id", "2","name_s", "mark", "title_s", "VP", "dept_ss_dv","Marketing",
"text_t","These guys make you look good")));
assertU(add(doc("id", "3","name_s", "nancy", "title_s", "MTS", "dept_ss_dv","Sales",
"text_t","These guys sell stuff")));
assertU(add(doc("id", "4","name_s", "dave", "title_s", "MTS", "dept_ss_dv","Support", "dept_ss_dv","Engineering"
, "text_t","These guys help customers")));
assertU(add(doc("id", "5","name_s", "tina", "title_s", "VP", "dept_ss_dv","Engineering",
"text_t","These guys develop stuff")));
assertU(commit());
update(fromCore, add(doc("id","10", "dept_id_s", "Engineering", "text_t","These guys develop stuff", "salary_i_dv", "1000")));
update(fromCore, add(doc("id","11", "dept_id_s", "Marketing", "text_t","These guys make you look good","salary_i_dv", "1500")));
update(fromCore, add(doc("id","12", "dept_id_s", "Sales", "text_t","These guys sell stuff","salary_i_dv", "1600")));
update(fromCore, add(doc("id","13", "dept_id_s", "Support", "text_t","These guys help customers","salary_i_dv", "800")));
update(fromCore, commit());
}
public static String update(SolrCore core, String xml) throws Exception {
DirectSolrConnection connection = new DirectSolrConnection(core);
SolrRequestHandler handler = core.getRequestHandler("/update");
return connection.request(handler, null, xml);
}
@Test
public void testSameCoreSingleField() throws Exception {
assertQ("subq1.fl is limited to single field",
req("q","name_s:john",
"fl","*,depts:[subquery fromIndex=fromCore]",
"depts.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"depts.fl","text_t"),
"//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts']/doc/str[@name='text_t'][.='These guys develop stuff']",
"count(//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts']/doc/*)=1");// only text_t
}
@Test
public void testAbsentCore() throws Exception {
assertQEx("from index not exist",
req("q","name_s:dave",
"fl","*,depts:[subquery fromIndex=fromCore2]",
"depts.q","{!term f=dept_id_s v=$row.dept_ss_dv}",
"depts.fl","text_t"),
SolrException.ErrorCode.BAD_REQUEST
);
}
@Test
public void testCrossCoreSubQueryTransformer() throws Exception {
assertQ("make sure request is parsed in this core",
req("q","name_s:john",
"fl","*,depts:[subquery]",
// text is tokenized and can be found, despite there is no substitution magic
"depts.q","{!field f=text_t}These guys"),
"//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts']/doc"
);
assertQ("make sure request is parsed in that core",
req("q","name_s:john",
"fl","*,depts:[subquery fromIndex=fromCore]",
// text is NOT tokenized and can NOT be found
"depts.q","{!field f=text_t}These guys"),
"count(//result/doc/str[@name='name_s_dv'][.='john']/../result[@name='depts']/doc)=0"
);
assertQ("make sure request is parsed in that core",
req("q","-name_s:dave", "indent", "true",
"fl","*,depts:[subquery fromIndex=fromCore]",
// stored text (text_t is string in minimal configset) can be found as
"depts.q","{!field f=text_t v=$row.text_t}",
"depts.fl", "dept_id_s" ),
"//result/doc/str[@name='name_s_dv'][.='john']/.."
+ "/result[@name='depts']/doc/str[@name='dept_id_s'][.='Engineering']",
"//result/doc/str[@name='name_s_dv'][.='tina']/.."
+ "/result[@name='depts']/doc/str[@name='dept_id_s'][.='Engineering']",
"//result/doc/str[@name='name_s_dv'][.='mark']/.."
+ "/result[@name='depts']/doc/str[@name='dept_id_s'][.='Marketing']"
);
}
@AfterClass
public static void nukeAll() {
fromCore = null;
}
}

View File

@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.response.transform;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.ContentStreamBase;
import org.junit.Test;
@SuppressSSL
public class TestSubQueryTransformerDistrib extends AbstractFullDistribZkTestBase {
@Override
protected String getCloudSchemaFile() {
return "schema-docValuesJoin.xml";
}
@Override
protected String getCloudSolrConfig() {
return "solrconfig-basic.xml";
}
@SuppressWarnings("serial")
@Test
public void test() throws SolrServerException, IOException {
int peopleMultiplier = atLeast(1);
int deptMultiplier = atLeast(1);
String people = "people";
int numPeopleShards;
createCollection(people, atLeast(1), numPeopleShards = atLeast(2), numPeopleShards);
String depts = "departments";
int numDeptsShards;
createCollection(depts, atLeast(1), numDeptsShards = atLeast(2), numDeptsShards);
createIndex(people, peopleMultiplier, depts, deptMultiplier);
Random random1 = random();
{
final QueryRequest qr = new QueryRequest(params(
new String[]{"q","name_s:dave", "indent","true",
"fl","*,depts:[subquery "+((random1.nextBoolean() ? "" : "separator=,"))+"]",
"rows","" + peopleMultiplier,
"depts.q","{!terms f=dept_id_s v=$row.dept_ss_dv "+((random1.nextBoolean() ? "" : "separator=,"))+"}",
"depts.fl","text_t",
"depts.indent","true",
"depts.collection","departments",
"depts.rows",""+(deptMultiplier*2),
"depts.logParamsList","q,fl,rows,row.dept_ss_dv"}));
final QueryResponse rsp = new QueryResponse();
rsp.setResponse(cloudClient.request(qr, people));
final SolrDocumentList hits = rsp.getResults();
assertEquals(peopleMultiplier, hits.getNumFound());
Map<String,String> engText = new HashMap<String,String>() {
{ put("text_t", "These guys develop stuff");
}
};
Map<String,String> suppText = new HashMap<String,String>() {
{ put("text_t", "These guys help customers");
}
};
int engineer = 0;
int support = 0;
for (int res : new int [] {0, (peopleMultiplier-1) /2, peopleMultiplier-1}) {
SolrDocument doc = hits.get(res);
assertEquals("dave", doc.getFieldValue("name_s_dv"));
SolrDocumentList relDepts = (SolrDocumentList) doc.getFieldValue("depts");
assertEquals("dave works in both depts "+rsp,
deptMultiplier * 2, relDepts.getNumFound());
for (int deptN = 0 ; deptN < relDepts.getNumFound(); deptN++ ) {
SolrDocument deptDoc = relDepts.get(deptN);
assertTrue(deptDoc + "should be either "+engText +" or "+suppText,
(engText.equals(deptDoc) && ++engineer>0) ||
(suppText.equals(deptDoc) && ++support>0));
}
}
assertEquals(hits.toString(), engineer, support);
}
}
private void createIndex(String people, int peopleMultiplier, String depts, int deptMultiplier)
throws SolrServerException, IOException {
int id=0;
List<String> peopleDocs = new ArrayList<>();
for (int p=0; p < peopleMultiplier; p++){
peopleDocs.add(add(doc("id", ""+id++,"name_s", "john", "title_s", "Director",
"dept_ss_dv","Engineering",
"dept_i", "0",
"dept_is", "0")));
peopleDocs.add(add(doc("id", ""+id++,"name_s", "mark", "title_s", "VP",
"dept_ss_dv","Marketing",
"dept_i", "1",
"dept_is", "1")));
peopleDocs.add(add(doc("id", ""+id++,"name_s", "nancy", "title_s", "MTS",
"dept_ss_dv","Sales",
"dept_i", "2",
"dept_is", "2")));
peopleDocs.add(add(doc("id", ""+id++,"name_s", "dave", "title_s", "MTS",
"dept_ss_dv","Support", "dept_ss_dv","Engineering",
"dept_i", "3",
"dept_is", "3", "dept_is", "0")));
peopleDocs.add(add(doc("id", ""+id++,"name_s", "tina", "title_s", "VP",
"dept_ss_dv","Engineering",
"dept_i", "0",
"dept_is", "0")));
}
addDocs(people, peopleDocs);
List<String> deptsDocs = new ArrayList<>();
for (int d=0; d < deptMultiplier; d++) {
deptsDocs.add(add(doc("id",""+id++, "dept_id_s", "Engineering", "text_t","These guys develop stuff", "salary_i_dv", "1000",
"dept_id_i", "0")));
deptsDocs.add(add(doc("id",""+id++, "dept_id_s", "Marketing", "text_t","These guys make you look good","salary_i_dv", "1500",
"dept_id_i", "1")));
deptsDocs.add(add(doc("id",""+id++, "dept_id_s", "Sales", "text_t","These guys sell stuff","salary_i_dv", "1600",
"dept_id_i", "2")));
deptsDocs.add(add(doc("id",""+id++, "dept_id_s", "Support", "text_t","These guys help customers","salary_i_dv", "800",
"dept_id_i", "3")));
}
addDocs(depts, deptsDocs);
}
private void addDocs(String collection, List<String> docs) throws SolrServerException, IOException {
StringBuilder upd = new StringBuilder("<update>");
for (Iterator<String> iterator = docs.iterator(); iterator.hasNext();) {
String add = iterator.next();
upd.append(add);
if (rarely()) {
upd.append(commit("softCommit", "true"));
}
if (!rarely() || !iterator.hasNext()) {
if (!iterator.hasNext()) {
upd.append(commit("softCommit", "false"));
}
upd.append("</update>");
ContentStreamUpdateRequest req = new ContentStreamUpdateRequest("/update");
req.addContentStream(new ContentStreamBase.StringStream(upd.toString(),"text/xml"));
cloudClient.request(req, collection);
upd.setLength("<update>".length());
}
}
}
}

View File

@ -70,7 +70,7 @@ public abstract class AbstractDistribZkTestBase extends BaseDistributedSearchTes
System.setProperty(ZOOKEEPER_FORCE_SYNC, "false");
System.setProperty(MockDirectoryFactory.SOLR_TESTS_ALLOW_READING_FILES_STILL_OPEN_FOR_WRITE, "true");
String schema = getSchemaFile();
String schema = getCloudSchemaFile();
if (schema == null) schema = "schema.xml";
AbstractZkTestCase.buildZooKeeper(zkServer.getZkHost(), zkServer.getZkAddress(), getCloudSolrConfig(), schema);
@ -83,6 +83,10 @@ public abstract class AbstractDistribZkTestBase extends BaseDistributedSearchTes
return "solrconfig-tlog.xml";
}
protected String getCloudSchemaFile() {
return getSchemaFile();
}
@Override
protected void createServers(int numShards) throws Exception {
// give everyone there own solrhome