SOLR-4799 faster join using join=zipper aka merge join for nested DIH EntityProcessors

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1643097 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Noble Paul 2014-12-03 11:56:26 +00:00
parent 854e3f930e
commit 83f0d3578c
9 changed files with 384 additions and 90 deletions

View File

@ -219,6 +219,9 @@ New Features
* SOLR-6607: Managing requesthandlers throuh API (Noble Paul)
* SOLR-4799: faster join using join="zipper" aka merge join for nested DIH EntityProcessors
(Mikhail Khludnev via Noble Paul)
Bug Fixes
----------------------

View File

@ -69,6 +69,7 @@ public class TikaEntityProcessor extends EntityProcessorBase {
@Override
protected void firstInit(Context context) {
super.firstInit(context);
try {
String tikaConfigFile = context.getResolvedEntityAttribute("tikaConfig");
if (tikaConfigFile == null) {

View File

@ -44,28 +44,11 @@ public class DIHCacheSupport {
public DIHCacheSupport(Context context, String cacheImplName) {
this.cacheImplName = cacheImplName;
String where = context.getEntityAttribute("where");
String cacheKey = context.getEntityAttribute(DIHCacheSupport.CACHE_PRIMARY_KEY);
String lookupKey = context.getEntityAttribute(DIHCacheSupport.CACHE_FOREIGN_KEY);
if (cacheKey != null && lookupKey == null) {
throw new DataImportHandlerException(DataImportHandlerException.SEVERE,
"'cacheKey' is specified for the entity "
+ context.getEntityAttribute("name")
+ " but 'cacheLookup' is missing");
}
if (where == null && cacheKey == null) {
cacheDoKeyLookup = false;
} else {
if (where != null) {
String[] splits = where.split("=");
cacheKey = splits[0];
cacheForeignKey = splits[1].trim();
} else {
cacheForeignKey = lookupKey;
}
cacheDoKeyLookup = true;
}
Relation r = new Relation(context);
cacheDoKeyLookup = r.doKeyLookup;
String cacheKey = r.primaryKey;
cacheForeignKey = r.foreignKey;
context.setSessionAttribute(DIHCacheSupport.CACHE_PRIMARY_KEY, cacheKey,
Context.SCOPE_ENTITY);
context.setSessionAttribute(DIHCacheSupport.CACHE_FOREIGN_KEY, cacheForeignKey,
@ -76,6 +59,48 @@ public class DIHCacheSupport {
Context.SCOPE_ENTITY);
}
static class Relation{
protected final boolean doKeyLookup;
protected final String foreignKey;
protected final String primaryKey;
public Relation(Context context) {
String where = context.getEntityAttribute("where");
String cacheKey = context.getEntityAttribute(DIHCacheSupport.CACHE_PRIMARY_KEY);
String lookupKey = context.getEntityAttribute(DIHCacheSupport.CACHE_FOREIGN_KEY);
if (cacheKey != null && lookupKey == null) {
throw new DataImportHandlerException(DataImportHandlerException.SEVERE,
"'cacheKey' is specified for the entity "
+ context.getEntityAttribute("name")
+ " but 'cacheLookup' is missing");
}
if (where == null && cacheKey == null) {
doKeyLookup = false;
primaryKey = null;
foreignKey = null;
} else {
if (where != null) {
String[] splits = where.split("=");
primaryKey = splits[0];
foreignKey = splits[1].trim();
} else {
primaryKey = cacheKey;
foreignKey = lookupKey;
}
doKeyLookup = true;
}
}
@Override
public String toString() {
return "Relation "
+ primaryKey + "="+foreignKey ;
}
}
private DIHCache instantiateCache(Context context) {
DIHCache cache = null;
try {

View File

@ -48,6 +48,8 @@ public class EntityProcessorBase extends EntityProcessor {
protected String onError = ABORT;
protected DIHCacheSupport cacheSupport = null;
protected Zipper zipper;
@Override
@ -56,19 +58,30 @@ public class EntityProcessorBase extends EntityProcessor {
if (isFirstInit) {
firstInit(context);
}
if(cacheSupport!=null) {
cacheSupport.initNewParent(context);
}
if(zipper!=null){
zipper.onNewParent(context);
}else{
if(cacheSupport!=null) {
cacheSupport.initNewParent(context);
}
}
}
/**first time init call. do one-time operations here
/**
* first time init call. do one-time operations here
* it's necessary to call it from the overridden method,
* otherwise it throws NPE on accessing zipper from nextRow()
*/
protected void firstInit(Context context) {
entityName = context.getEntityAttribute("name");
String s = context.getEntityAttribute(ON_ERROR);
if (s != null) onError = s;
initCache(context);
zipper = Zipper.createOrNull(context);
if(zipper==null){
initCache(context);
}
isFirstInit = false;
}
@ -109,25 +122,29 @@ public class EntityProcessorBase extends EntityProcessor {
}
protected Map<String, Object> getNext() {
if(cacheSupport==null) {
try {
if (rowIterator == null)
if(zipper!=null){
return zipper.supplyNextChild(rowIterator);
}else{
if(cacheSupport==null) {
try {
if (rowIterator == null)
return null;
if (rowIterator.hasNext())
return rowIterator.next();
query = null;
rowIterator = null;
return null;
if (rowIterator.hasNext())
return rowIterator.next();
query = null;
rowIterator = null;
return null;
} catch (Exception e) {
SolrException.log(log, "getNext() failed for query '" + query + "'", e);
query = null;
rowIterator = null;
wrapAndThrow(DataImportHandlerException.WARN, e);
return null;
}
} else {
return cacheSupport.getCacheData(context, query, rowIterator);
}
} catch (Exception e) {
SolrException.log(log, "getNext() failed for query '" + query + "'", e);
query = null;
rowIterator = null;
wrapAndThrow(DataImportHandlerException.WARN, e);
return null;
}
} else {
return cacheSupport.getCacheData(context, query, rowIterator);
}
}
}

View File

@ -0,0 +1,116 @@
package org.apache.solr.handler.dataimport;
import java.util.Iterator;
import java.util.Map;
import org.apache.solr.handler.dataimport.DIHCacheSupport.Relation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
/*
* 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.
*/
class Zipper {
private static final Logger log = LoggerFactory.getLogger(Zipper.class);
private final DIHCacheSupport.Relation relation;
@SuppressWarnings("rawtypes")
private Comparable parentId;
@SuppressWarnings("rawtypes")
private Comparable lastChildId;
private Iterator<Map<String,Object>> rowIterator;
private PeekingIterator<Map<String,Object>> peeker;
/** @return initialized zipper or null */
public static Zipper createOrNull(Context context){
if("zipper".equals(context.getEntityAttribute("join"))){
DIHCacheSupport.Relation r = new DIHCacheSupport.Relation(context);
if(r.doKeyLookup){
return new Zipper(r);
}
}
return null;
}
private Zipper(Relation relation) {
this.relation = relation;
}
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String,Object> supplyNextChild(
Iterator<Map<String,Object>> rowIterator) {
preparePeeker(rowIterator);
while(peeker.hasNext()){
Map<String,Object> current = peeker.peek();
Comparable childId = (Comparable) current.get(relation.primaryKey);
if(lastChildId!=null && lastChildId.compareTo(childId)>0){
throw new IllegalArgumentException("expect increasing foreign keys for "+relation+
" got: "+lastChildId+","+childId);
}
lastChildId = childId;
int cmp = childId.compareTo(parentId);
if(cmp==0){
Map<String,Object> child = peeker.next();
assert child==current: "peeker should be right but "+current+" != " + child;
log.trace("yeild child {} entry {}",relation, current);
return child;// TODO it's for one->many for many->one it should be just peek()
}else{
if(cmp<0){ // child belongs to 10th and parent is 20th, skip for the next one
Map<String,Object> child = peeker.next();
assert child==current: "peeker should be right but "+current+" != " + child;
log.trace("skip child {}, {} > {}",relation, parentId, childId);
}else{ // child belongs to 20th and parent is 10th, no more children, go to next parent
log.trace("childen is over {}, {} < {}", relation, parentId, current);
return null;
}
}
}
return null;
}
private void preparePeeker(Iterator<Map<String,Object>> rowIterator) {
if(this.rowIterator==null){
this.rowIterator = rowIterator;
peeker = Iterators.peekingIterator(rowIterator);
}else{
assert this.rowIterator==rowIterator: "rowIterator should never change but "+this.rowIterator+
" supplied before has been changed to "+rowIterator;
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
public void onNewParent(Context context) {
Comparable newParent = (Comparable) context.resolve(relation.foreignKey);
if(parentId!=null && parentId.compareTo(newParent)>=0){
throw new IllegalArgumentException("expect strictly increasing primary keys for "+relation+
" got: "+parentId+","+newParent);
}
log.trace("{}: {}->{}",relation, newParent, parentId);
parentId = newParent;
}
}

View File

@ -45,8 +45,15 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
protected boolean useSimpleCaches;
protected boolean countryEntity;
protected boolean countryCached;
protected boolean countryZipper;
protected boolean sportsEntity;
protected boolean sportsCached;
protected boolean sportsZipper;
protected boolean wrongPeopleOrder ;
protected boolean wrongSportsOrder ;
protected boolean wrongCountryOrder;
protected String rootTransformerName;
protected boolean countryTransformer;
protected boolean sportsTransformer;
@ -65,8 +72,15 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
useSimpleCaches = false;
countryEntity = false;
countryCached = false;
countryZipper = false;
sportsEntity = false;
sportsCached = false;
sportsZipper = false;
wrongPeopleOrder = false;
wrongSportsOrder = false;
wrongCountryOrder= false;
rootTransformerName = null;
countryTransformer = false;
sportsTransformer = false;
@ -177,9 +191,14 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
} else {
countryEntity = true;
sportsEntity = true;
if (numChildren == 1) {
countryEntity = random().nextBoolean();
sportsEntity = !countryEntity;
if(countryZipper||sportsZipper){// zipper tests fully cover nums of children
countryEntity = countryZipper;
sportsEntity = sportsZipper;
}else{// apply default randomization on cached cases
if (numChildren == 1) {
countryEntity = random().nextBoolean();
sportsEntity = !countryEntity;
}
}
if (countryEntity) {
countryTransformer = random().nextBoolean();
@ -212,18 +231,29 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
+ (totalPeople()) + "']");
}
if (countryEntity) {
if (personNameExists("Jayden")) {
String nrName = countryNameByCode("NP");
if (nrName != null && nrName.length() > 0) {
assertQ(req("NAME_mult_s:Jayden"), "//*[@numFound='1']",
"//doc/str[@name='COUNTRY_NAME_s']='" + nrName + "'");
}
{
String[] people = getStringsFromQuery("SELECT NAME FROM PEOPLE WHERE DELETED != 'Y'");
String man = people[random().nextInt(people.length)];
String[] countryNames = getStringsFromQuery("SELECT C.COUNTRY_NAME FROM PEOPLE P "
+ "INNER JOIN COUNTRIES C ON P.COUNTRY_CODE=C.CODE "
+ "WHERE P.DELETED!='Y' AND C.DELETED!='Y' AND P.NAME='" + man + "'");
assertQ(req("{!term f=NAME_mult_s}"+ man), "//*[@numFound='1']",
countryNames.length>0?
"//doc/str[@name='COUNTRY_NAME_s']='" + countryNames[random().nextInt(countryNames.length)] + "'"
:"//doc[count(*[@name='COUNTRY_NAME_s'])=0]");
}
String nrName = countryNameByCode("NR");
int num = numberPeopleByCountryCode("NR");
if (nrName != null && num > 0) {
assertQ(req("COUNTRY_CODES_mult_s:NR"), "//*[@numFound='" + num + "']",
"//doc/str[@name='COUNTRY_NAME_s']='" + nrName + "'");
{
String[] countryCodes = getStringsFromQuery("SELECT CODE FROM COUNTRIES WHERE DELETED != 'Y'");
String theCode = countryCodes[random().nextInt(countryCodes.length)];
int num = numberPeopleByCountryCode(theCode);
if(num>0){
String nrName = countryNameByCode(theCode);
assertQ(req("COUNTRY_CODES_mult_s:"+theCode), "//*[@numFound='" + num + "']",
"//doc/str[@name='COUNTRY_NAME_s']='" + nrName + "'");
}else{ // no one lives there anyway
assertQ(req("COUNTRY_CODES_mult_s:"+theCode), "//*[@numFound='" + num + "']");
}
}
if (countryTransformer && !underlyingDataModified) {
assertQ(req("countryAdded_s:country_added"), "//*[@numFound='"
@ -234,24 +264,27 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
if (!underlyingDataModified) {
assertQ(req("SPORT_NAME_mult_s:Sailing"), "//*[@numFound='2']");
}
String michaelsName = personNameById(3);
String[] michaelsSports = sportNamesByPersonId(3);
if (michaelsName != null && michaelsSports.length > 0) {
String[] xpath = new String[michaelsSports.length + 1];
xpath[0] = "//*[@numFound='1']";
int i = 1;
for (String ms : michaelsSports) {
xpath[i] = "//doc/arr[@name='SPORT_NAME_mult_s']/str[" + i + "]='"
+ ms + "'";
i++;
}
assertQ(req("NAME_mult_s:" + michaelsName.replaceAll("\\W", "\\\\$0")),
xpath);
String [] names = getStringsFromQuery("SELECT NAME FROM PEOPLE WHERE DELETED != 'Y'");
String name = names[random().nextInt(names.length)];
int personId = getIntFromQuery("SELECT ID FROM PEOPLE WHERE DELETED != 'Y' AND NAME='"+name+"'");
String[] michaelsSports = sportNamesByPersonId(personId);
String[] xpath = new String[michaelsSports.length + 1];
xpath[0] = "//*[@numFound='1']";
int i = 1;
for (String ms : michaelsSports) {
xpath[i] = "//doc/arr[@name='SPORT_NAME_mult_s']/str='"//[" + i + "]='" don't care about particular order
+ ms + "'";
i++;
}
assertQ(req("NAME_mult_s:" + name.replaceAll("\\W", "\\\\$0")),
xpath);
if (!underlyingDataModified && sportsTransformer) {
assertQ(req("sportsAdded_s:sport_added"), "//*[@numFound='"
+ (totalPeople()) + "']");
+ (totalSportsmen()) + "']");
}
assertQ("checking orphan sport is absent",
req("{!term f=SPORT_NAME_mult_s}No Fishing"), "//*[@numFound='0']");
}
if (checkDatabaseRequests) {
Assert.assertTrue("Expecting " + numDatabaseRequests
@ -304,6 +337,7 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
}
}
private int getIntFromQuery(String query) throws Exception {
Connection conn = null;
Statement s = null;
@ -367,6 +401,12 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
return getIntFromQuery("SELECT COUNT(1) FROM PEOPLE WHERE DELETED != 'Y' ");
}
public int totalSportsmen() throws Exception {
return getIntFromQuery("SELECT COUNT(*) FROM PEOPLE WHERE "
+ "EXISTS(SELECT ID FROM PEOPLE_SPORTS WHERE PERSON_ID=PEOPLE.ID AND PEOPLE_SPORTS.DELETED != 'Y')"
+ " AND PEOPLE.DELETED != 'Y'");
}
public boolean countryCodeExists(String cc) throws Exception {
return getIntFromQuery("SELECT COUNT(1) country_name FROM COUNTRIES WHERE DELETED != 'Y' AND CODE='"
+ cc + "'") > 0;
@ -574,7 +614,11 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
sb.append("dataSource=''" + ds + "'' ");
sb.append(rootTransformerName != null ? "transformer=''"
+ rootTransformerName + "'' " : "");
sb.append("query=''SELECT ID, NAME, COUNTRY_CODE FROM PEOPLE WHERE DELETED != 'Y' '' ");
sb.append("query=''SELECT ID, NAME, COUNTRY_CODE FROM PEOPLE WHERE DELETED != 'Y' "
+((sportsZipper||countryZipper?"ORDER BY ID":"")
+(wrongPeopleOrder? " DESC":""))+"'' ");
sb.append(deltaQueriesPersonTable());
sb.append("> \n");
@ -595,9 +639,20 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
if (useSimpleCaches) {
sb.append("query=''SELECT CODE, COUNTRY_NAME FROM COUNTRIES WHERE DELETED != 'Y' AND CODE='${People.COUNTRY_CODE}' ''>\n");
} else {
sb.append(random().nextBoolean() ? "cacheKey=''CODE'' cacheLookup=''People.COUNTRY_CODE'' "
: "where=''CODE=People.COUNTRY_CODE'' ");
sb.append("query=''SELECT CODE, COUNTRY_NAME FROM COUNTRIES'' ");
if(countryZipper){// really odd join btw. it sends duped countries
sb.append(random().nextBoolean() ? "cacheKey=''ID'' cacheLookup=''People.ID'' "
: "where=''ID=People.ID'' ");
sb.append("join=''zipper'' query=''SELECT PEOPLE.ID, CODE, COUNTRY_NAME FROM COUNTRIES"
+ " JOIN PEOPLE ON COUNTRIES.CODE=PEOPLE.COUNTRY_CODE "
+ "WHERE PEOPLE.DELETED != 'Y' ORDER BY PEOPLE.ID "+
(wrongCountryOrder ? " DESC":"")
+ "'' ");
}else{
sb.append(random().nextBoolean() ? "cacheKey=''CODE'' cacheLookup=''People.COUNTRY_CODE'' "
: "where=''CODE=People.COUNTRY_CODE'' ");
sb.append("query=''SELECT CODE, COUNTRY_NAME FROM COUNTRIES'' ");
}
sb.append("> \n");
}
} else {
@ -623,7 +678,14 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
} else {
sb.append(random().nextBoolean() ? "cacheKey=''PERSON_ID'' cacheLookup=''People.ID'' "
: "where=''PERSON_ID=People.ID'' ");
sb.append("query=''SELECT ID, PERSON_ID, SPORT_NAME FROM PEOPLE_SPORTS ORDER BY ID'' ");
if(sportsZipper){
sb.append("join=''zipper'' query=''SELECT ID, PERSON_ID, SPORT_NAME FROM PEOPLE_SPORTS ORDER BY PERSON_ID"
+ (wrongSportsOrder?" DESC" : "")+
"'' ");
}
else{
sb.append("query=''SELECT ID, PERSON_ID, SPORT_NAME FROM PEOPLE_SPORTS ORDER BY ID'' ");
}
}
} else {
sb.append("processor=''SqlEntityProcessor'' query=''SELECT ID, SPORT_NAME FROM PEOPLE_SPORTS WHERE DELETED != 'Y' AND PERSON_ID=${People.ID} ORDER BY ID'' ");
@ -726,7 +788,9 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
{7,"Noah","NI"},
{8,"Daniel","NG"},
{9,"Aiden","NF"},
{10,"Anthony","NE"},
{21,"Anthony","NE"}, // there is no ID=10 anymore
{11,"Emma","NL"},
{12,"Grace","NI"},
{13,"Hailey","NG"},
@ -751,7 +815,10 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
{700, 7, "Boating"},
{800, 8, "Bodyboarding"},
{900, 9, "Canoeing"},
{1000, 10, "Fishing"},
{1000, 10, "No Fishing"}, // orhpaned sport
//
{1100, 11, "Jet Ski"},
{1110, 11, "Rowing"},
{1120, 11, "Sailing"},
@ -760,10 +827,12 @@ public abstract class AbstractSqlEntityProcessorTestCase extends
{1300, 13, "Kite surfing"},
{1400, 14, "Parasailing"},
{1500, 15, "Rafting"},
{1600, 16, "Rowing"},
//{1600, 16, "Rowing"}, Madison has no sport
{1700, 17, "Sailing"},
{1800, 18, "White Water Rafting"},
{1900, 19, "Water skiing"},
{2000, 20, "Windsurfing"}
{2000, 20, "Windsurfing"},
{2100, 21, "Concrete diving"},
{2110, 21, "Bubble rugby"}
};
}

View File

@ -31,10 +31,6 @@ public class MockSolrEntityProcessor extends SolrEntityProcessor {
private int rows;
public MockSolrEntityProcessor(List<SolrTestCaseJ4.Doc> docsData) {
this(docsData, ROWS_DEFAULT);
}
public MockSolrEntityProcessor(List<SolrTestCaseJ4.Doc> docsData, int rows) {
this.docsData = docsData;
this.rows = rows;

View File

@ -31,20 +31,33 @@ public class TestSolrEntityProcessorUnit extends AbstractDataImportHandlerTestCa
public void testQuery() {
List<Doc> docs = generateUniqueDocs(2);
MockSolrEntityProcessor processor = new MockSolrEntityProcessor(docs);
MockSolrEntityProcessor processor = createAndInit(docs);
assertExpectedDocs(docs, processor);
assertEquals(1, processor.getQueryCount());
}
private MockSolrEntityProcessor createAndInit(List<Doc> docs) {
return createAndInit(docs, SolrEntityProcessor.ROWS_DEFAULT);
}
public void testNumDocsGreaterThanRows() {
List<Doc> docs = generateUniqueDocs(44);
MockSolrEntityProcessor processor = new MockSolrEntityProcessor(docs, 10);
int rowsNum = 10;
MockSolrEntityProcessor processor = createAndInit(docs, rowsNum);
assertExpectedDocs(docs, processor);
assertEquals(5, processor.getQueryCount());
}
private MockSolrEntityProcessor createAndInit(List<Doc> docs, int rowsNum) {
MockSolrEntityProcessor processor = new MockSolrEntityProcessor(docs, rowsNum);
HashMap<String,String> entityAttrs = new HashMap<String,String>(){{put(SolrEntityProcessor.SOLR_SERVER,"http://route:66/no");}};
processor.init(getContext(null, null, null, null, Collections.emptyList(),
entityAttrs));
return processor;
}
public void testMultiValuedFields() {
List<Doc> docs = new ArrayList<>();
List<FldType> types = new ArrayList<>();
@ -53,7 +66,7 @@ public class TestSolrEntityProcessorUnit extends AbstractDataImportHandlerTestCa
Doc testDoc = createDoc(types);
docs.add(testDoc);
MockSolrEntityProcessor processor = new MockSolrEntityProcessor(docs);
MockSolrEntityProcessor processor = createAndInit(docs);
Map<String, Object> next = processor.nextRow();
assertNotNull(next);

View File

@ -45,6 +45,60 @@ public class TestSqlEntityProcessor extends AbstractSqlEntityProcessorTestCase {
public void testCachedChildEntities() throws Exception {
withChildEntities(true, true);
}
@Test
public void testSportZipperChildEntities() throws Exception {
sportsZipper = true;
withChildEntities(true, true);
}
@Test
public void testCountryZipperChildEntities() throws Exception {
countryZipper = true;
withChildEntities(true, true);
}
@Test
public void testBothZipperChildEntities() throws Exception {
countryZipper = true;
sportsZipper = true;
withChildEntities(true, true);
}
@Test(expected=RuntimeException.class /* DIH exceptions are not propagated, here we capturing assertQ exceptions */)
public void testSportZipperChildEntitiesWrongOrder() throws Exception {
if(random().nextBoolean()){
wrongPeopleOrder = true;
}else{
wrongSportsOrder = true;
}
testSportZipperChildEntities();
}
@Test(expected=RuntimeException.class )
public void testCountryZipperChildEntitiesWrongOrder() throws Exception {
if(random().nextBoolean()){
wrongPeopleOrder = true;
}else{
wrongCountryOrder = true;
}
testCountryZipperChildEntities();
}
@Test(expected=RuntimeException.class)
public void testBothZipperChildEntitiesWrongOrder() throws Exception {
if(random().nextBoolean()){
wrongPeopleOrder = true;
}else{
if(random().nextBoolean()){
wrongSportsOrder = true;
}else{
wrongCountryOrder = true;
}
}
testBothZipperChildEntities();
}
@Test
@Ignore("broken see SOLR-3857")
public void testSimpleCacheChildEntities() throws Exception {