SOLR-10939: add point support to join query

This commit is contained in:
yonik 2017-08-07 12:29:28 -04:00
parent 5fb800f018
commit bd5c09b1ee
8 changed files with 252 additions and 203 deletions

View File

@ -343,6 +343,9 @@ New Features
* SOLR-10845: Add support for PointFields to {!graphTerms} query that is internally
used by some graph traversal streaming expressions. (yonik)
* SOLR-10939: Add support for PointsFields to {!join} query. Joined fields should
also have docValues enabled. (yonik)
Bug Fixes
----------------------
* SOLR-9262: Connection and read timeouts are being ignored by UpdateShardHandler after SOLR-4509.

View File

@ -52,7 +52,9 @@ import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.TrieField;
import org.apache.solr.search.join.GraphPointsCollector;
import org.apache.solr.search.join.ScoreJoinQParserPlugin;
import org.apache.solr.util.RTimer;
import org.apache.solr.util.RefCounted;
@ -281,6 +283,7 @@ class JoinQuery extends Query {
}
// most of these statistics are only used for the enum method
int fromSetSize; // number of docs in the fromSet (that match the from query)
long resultListDocs; // total number of docs collected
int fromTermCount;
@ -295,6 +298,33 @@ class JoinQuery extends Query {
public DocSet getDocSet() throws IOException {
SchemaField fromSchemaField = fromSearcher.getSchema().getField(fromField);
SchemaField toSchemaField = toSearcher.getSchema().getField(toField);
boolean usePoints = false;
if (toSchemaField.getType().isPointField()) {
if (!fromSchemaField.hasDocValues()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "join from field " + fromSchemaField + " should have docValues to join with points field " + toSchemaField);
}
usePoints = true;
}
if (!usePoints) {
return getDocSetEnumerate();
}
// point fields
GraphPointsCollector collector = new GraphPointsCollector(fromSchemaField, null, null);
fromSearcher.search(q, collector);
Query resultQ = collector.getResultQuery(toSchemaField, false);
// don't cache the resulting docSet... the query may be very large. Better to cache the results of the join query itself
DocSet result = resultQ==null ? DocSet.EMPTY : toSearcher.getDocSetNC(resultQ, null);
return result;
}
public DocSet getDocSetEnumerate() throws IOException {
FixedBitSet resultBits = null;
// minimum docFreq to use the cache

View File

@ -0,0 +1,122 @@
/*
* 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.search.join;
import java.io.IOException;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.NumericUtils;
import org.apache.solr.schema.NumberType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocSet;
import org.apache.solr.util.LongIterator;
import org.apache.solr.util.LongSet;
/** @lucene.internal */
public class GraphPointsCollector extends GraphEdgeCollector {
final LongSet set = new LongSet(256);
SortedNumericDocValues values = null;
public GraphPointsCollector(SchemaField collectField, DocSet skipSet, DocSet leafNodes) {
super(collectField, skipSet, leafNodes);
}
@Override
public void doSetNextReader(LeafReaderContext context) throws IOException {
super.doSetNextReader(context);
values = DocValues.getSortedNumeric(context.reader(), collectField.getName());
}
@Override
void addEdgeIdsToResult(int doc) throws IOException {
// set the doc to pull the edges ids for.
int valuesDoc = values.docID();
if (valuesDoc < doc) {
valuesDoc = values.advance(doc);
}
if (valuesDoc == doc) {
int count = values.docValueCount();
for (int i = 0; i < count; i++) {
long v = values.nextValue();
set.add(v);
}
}
}
@Override
public Query getResultQuery(SchemaField matchField, boolean useAutomaton) {
if (set.cardinality() == 0) return null;
Query q = null;
// How we interpret the longs collected depends on the field we collect from (single valued can be diff from multi valued)
// The basic type of the from & to field must match though (int/long/float/double)
NumberType ntype = collectField.getType().getNumberType();
boolean multiValued = collectField.multiValued();
if (ntype == NumberType.LONG || ntype == NumberType.DATE) {
long[] vals = new long[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
long v = bits;
vals[i++] = v;
}
q = LongPoint.newSetQuery(matchField.getName(), vals);
} else if (ntype == NumberType.INTEGER) {
int[] vals = new int[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
int v = (int)bits;
vals[i++] = v;
}
q = IntPoint.newSetQuery(matchField.getName(), vals);
} else if (ntype == NumberType.DOUBLE) {
double[] vals = new double[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
double v = multiValued ? NumericUtils.sortableLongToDouble(bits) : Double.longBitsToDouble(bits);
vals[i++] = v;
}
q = DoublePoint.newSetQuery(matchField.getName(), vals);
} else if (ntype == NumberType.FLOAT) {
float[] vals = new float[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
float v = multiValued ? NumericUtils.sortableIntToFloat((int) bits) : Float.intBitsToFloat((int) bits);
vals[i++] = v;
}
q = FloatPoint.newSetQuery(matchField.getName(), vals);
}
return q;
}
}

View File

@ -25,6 +25,7 @@ import java.util.TreeSet;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DocIdSet;
@ -133,15 +134,15 @@ public class GraphQuery extends Query {
private int currentDepth = -1;
private Filter filter;
private DocSet resultSet;
SchemaField fromSchemaField;
SchemaField toSchemaField;
SchemaField collectSchemaField; // the field to collect values from
SchemaField matchSchemaField; // the field to match those values
public GraphQueryWeight(SolrIndexSearcher searcher, float boost) {
// Grab the searcher so we can run additional searches.
super(null);
this.fromSearcher = searcher;
this.fromSchemaField = searcher.getSchema().getField(fromField);
this.toSchemaField = searcher.getSchema().getField(toField);
this.matchSchemaField = searcher.getSchema().getField(fromField);
this.collectSchemaField = searcher.getSchema().getField(toField);
}
GraphQuery getGraphQuery() {
@ -196,13 +197,25 @@ public class GraphQuery extends Query {
} else {
// when we're not at the max depth level, we need to collect edges
// Create the graph result collector for this level
GraphEdgeCollector graphResultCollector = toSchemaField.getType().isPointField()
? new GraphPointsCollector(this, capacity, resultBits, leafNodes)
: new GraphTermsCollector(this, capacity, resultBits, leafNodes);
GraphEdgeCollector graphResultCollector = collectSchemaField.getType().isPointField()
? new GraphPointsCollector(collectSchemaField, new BitDocSet(resultBits), leafNodes)
: new GraphTermsCollector(collectSchemaField, new BitDocSet(resultBits), leafNodes);
fromSet = new BitDocSet(new FixedBitSet(capacity));
graphResultCollector.setCollectDocs(fromSet.getBits());
fromSearcher.search(frontierQuery, graphResultCollector);
fromSet = graphResultCollector.getDocSet();
frontierQuery = graphResultCollector.getFrontierQuery();
frontierQuery = graphResultCollector.getResultQuery(matchSchemaField, isUseAutn());
// If there is a filter to be used while crawling the graph, add that.
if (frontierQuery != null && getTraversalFilter() != null) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(frontierQuery, BooleanClause.Occur.MUST);
builder.add(getTraversalFilter(), BooleanClause.Occur.MUST);
frontierQuery = builder.build();
}
}
if (currentDepth == 0 && !returnRoot) {
// grab a copy of the root bits but only if we need it.
@ -230,9 +243,9 @@ public class GraphQuery extends Query {
}
private DocSet resolveLeafNodes() throws IOException {
String field = toSchemaField.getName();
String field = collectSchemaField.getName();
BooleanQuery.Builder leafNodeQuery = new BooleanQuery.Builder();
Query edgeQuery = toSchemaField.hasDocValues() ? new DocValuesFieldExistsQuery(field) : new WildcardQuery(new Term(field, "*"));
Query edgeQuery = collectSchemaField.hasDocValues() ? new DocValuesFieldExistsQuery(field) : new WildcardQuery(new Term(field, "*"));
leafNodeQuery.add(edgeQuery, Occur.MUST_NOT);
DocSet leafNodes = fromSearcher.getDocSet(leafNodeQuery.build());
return leafNodes;

View File

@ -21,36 +21,23 @@ import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SimpleCollector;
import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.util.BitSet;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefHash;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.DaciukMihovAutomatonBuilder;
import org.apache.solr.schema.NumberType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.BitDocSet;
import org.apache.solr.search.DocSet;
import org.apache.solr.util.LongIterator;
import org.apache.solr.util.LongSet;
/**
* A graph hit collector. This accumulates the edges for a given graph traversal.
@ -59,49 +46,51 @@ import org.apache.solr.util.LongSet;
* @lucene.internal
*/
abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
GraphQuery.GraphQueryWeight weight;
// the result set that is being collected.
Bits currentResult;
// For graph traversal, the result set that has already been visited and thus can be skipped for during value collection.
DocSet skipSet;
// known leaf nodes
DocSet leafNodes;
// number of hits discovered at this level.
int numHits=0;
BitSet bits;
final int maxDoc;
int base;
int baseInParent;
// if we care to track this.
boolean hasCycles = false;
GraphEdgeCollector(GraphQuery.GraphQueryWeight weight, int maxDoc, Bits currentResult, DocSet leafNodes) {
this.weight = weight;
this.maxDoc = maxDoc;
this.currentResult = currentResult;
int numHits=0; // number of documents visited
BitSet bits; // if not null, used to collect documents visited
int base;
SchemaField collectField;
// skipSet and leafNodes may be null
GraphEdgeCollector(SchemaField collectField, DocSet skipSet, DocSet leafNodes) {
this.collectField = collectField;
this.skipSet = skipSet;
this.leafNodes = leafNodes;
if (bits==null) {
// create a bitset at the start that will hold the graph traversal result set
bits = new FixedBitSet(maxDoc);
}
}
// Set to use to collect docs being visited
// TODO: this should be replaced with a more general delegating collector
public void setCollectDocs(FixedBitSet target) {
this.bits = target;
}
// the number of docs visited
public int getNumHits() { return numHits; }
public void collect(int doc) throws IOException {
doc += base;
if (currentResult.get(doc)) {
// cycle detected / already been here.
// knowing if your graph had a cycle might be useful and it's lightweight to implement here.
hasCycles = true;
public void collect(int segDoc) throws IOException {
int doc = segDoc + base;
if (skipSet != null && skipSet.exists(doc)) {
// when skipSet == all nodes visited so far, then this represents a cycle and we can
// keep track of that here in the future if we need to.
return;
}
// collect the docs
addDocToResult(doc);
// Optimization to not look up edges for a document that is a leaf node
if (bits != null) bits.set(doc);
// increment the hit count so we know how many docs we traversed this time.
numHits++;
// Optimization to not look up edges for a document that is a leaf node (i.e. has no outgoing edges)
if (leafNodes == null || !leafNodes.exists(doc)) {
addEdgeIdsToResult(doc-base);
addEdgeIdsToResult(segDoc);
}
// Note: tracking links in for each result would be a huge memory hog... so not implementing at this time.
}
abstract void addEdgeIdsToResult(int doc) throws IOException;
@ -113,37 +102,13 @@ abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
numHits++;
}
public BitDocSet getDocSet() {
if (bits == null) {
// TODO: this shouldn't happen
bits = new FixedBitSet(maxDoc);
}
return new BitDocSet((FixedBitSet)bits,numHits);
}
@Override
public void doSetNextReader(LeafReaderContext context) throws IOException {
base = context.docBase;
baseInParent = context.docBaseInParent;
}
protected abstract Query getResultQuery();
public abstract Query getResultQuery(SchemaField matchField, boolean useAutomaton);
public Query getFrontierQuery() {
Query q = getResultQuery();
if (q == null) return null;
// If there is a filter to be used while crawling the graph, add that.
if (weight.getGraphQuery().getTraversalFilter() != null) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(q, BooleanClause.Occur.MUST);
builder.add(weight.getGraphQuery().getTraversalFilter(), BooleanClause.Occur.MUST);
q = builder.build();
}
return q;
}
@Override
public boolean needsScores() {
return false;
@ -157,8 +122,8 @@ class GraphTermsCollector extends GraphEdgeCollector {
private SortedSetDocValues docTermOrds;
GraphTermsCollector(GraphQuery.GraphQueryWeight weight, int maxDoc, Bits currentResult, DocSet leafNodes) {
super(weight, maxDoc, currentResult, leafNodes);
GraphTermsCollector(SchemaField collectField, DocSet skipSet, DocSet leafNodes) {
super(collectField, skipSet, leafNodes);
this.collectorTerms = new BytesRefHash();
}
@ -166,7 +131,7 @@ class GraphTermsCollector extends GraphEdgeCollector {
public void doSetNextReader(LeafReaderContext context) throws IOException {
super.doSetNextReader(context);
// Grab the updated doc values.
docTermOrds = DocValues.getSortedSet(context.reader(), weight.getGraphQuery().getToField());
docTermOrds = DocValues.getSortedSet(context.reader(), collectField.getName());
}
@Override
@ -187,7 +152,7 @@ class GraphTermsCollector extends GraphEdgeCollector {
}
@Override
protected Query getResultQuery() {
public Query getResultQuery(SchemaField matchField, boolean useAutomaton) {
if (collectorTerms == null || collectorTerms.size() == 0) {
// return null if there are no terms (edges) to traverse.
return null;
@ -195,12 +160,11 @@ class GraphTermsCollector extends GraphEdgeCollector {
// Create a query
Query q = null;
GraphQuery gq = weight.getGraphQuery();
// TODO: see if we should dynamically select this based on the frontier size.
if (gq.isUseAutn()) {
if (useAutomaton) {
// build an automaton based query for the frontier.
Automaton autn = buildAutomaton(collectorTerms);
AutomatonQuery autnQuery = new AutomatonQuery(new Term(gq.getFromField()), autn);
AutomatonQuery autnQuery = new AutomatonQuery(new Term(matchField.getName()), autn);
q = autnQuery;
} else {
List<BytesRef> termList = new ArrayList<>(collectorTerms.size());
@ -209,7 +173,7 @@ class GraphTermsCollector extends GraphEdgeCollector {
collectorTerms.get(i, ref);
termList.add(ref);
}
q = new TermInSetQuery(gq.getFromField(), termList);
q = new TermInSetQuery(matchField.getName(), termList);
}
return q;
@ -232,98 +196,3 @@ class GraphTermsCollector extends GraphEdgeCollector {
}
class GraphPointsCollector extends GraphEdgeCollector {
final LongSet set = new LongSet(256);
SortedNumericDocValues values = null;
GraphPointsCollector(GraphQuery.GraphQueryWeight weight, int maxDoc, Bits currentResult, DocSet leafNodes) {
super(weight, maxDoc, currentResult, leafNodes);
}
@Override
public void doSetNextReader(LeafReaderContext context) throws IOException {
super.doSetNextReader(context);
values = DocValues.getSortedNumeric(context.reader(), weight.getGraphQuery().getToField());
}
@Override
void addEdgeIdsToResult(int doc) throws IOException {
// set the doc to pull the edges ids for.
int valuesDoc = values.docID();
if (valuesDoc < doc) {
valuesDoc = values.advance(doc);
}
if (valuesDoc == doc) {
int count = values.docValueCount();
for (int i = 0; i < count; i++) {
long v = values.nextValue();
set.add(v);
}
}
}
@Override
protected Query getResultQuery() {
if (set.cardinality() == 0) return null;
Query q = null;
SchemaField sfield = weight.fromSchemaField;
NumberType ntype = sfield.getType().getNumberType();
boolean multiValued = sfield.multiValued();
if (ntype == NumberType.LONG || ntype == NumberType.DATE) {
long[] vals = new long[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
long v = bits;
vals[i++] = v;
}
q = LongPoint.newSetQuery(sfield.getName(), vals);
} else if (ntype == NumberType.INTEGER) {
int[] vals = new int[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
int v = (int)bits;
vals[i++] = v;
}
q = IntPoint.newSetQuery(sfield.getName(), vals);
} else if (ntype == NumberType.DOUBLE) {
double[] vals = new double[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
double v = multiValued ? NumericUtils.sortableLongToDouble(bits) : Double.longBitsToDouble(bits);
vals[i++] = v;
}
q = DoublePoint.newSetQuery(sfield.getName(), vals);
} else if (ntype == NumberType.FLOAT) {
float[] vals = new float[set.cardinality()];
int i = 0;
for (LongIterator iter = set.iterator(); iter.hasNext(); ) {
long bits = iter.next();
float v = multiValued ? NumericUtils.sortableIntToFloat((int) bits) : Float.intBitsToFloat((int) bits);
vals[i++] = v;
}
q = FloatPoint.newSetQuery(sfield.getName(), vals);
}
return q;
}
/** Build an automaton to represent the frontier query */
private Automaton buildAutomaton(BytesRefHash termBytesHash) {
// need top pass a sorted set of terms to the autn builder (maybe a better way to avoid this?)
final TreeSet<BytesRef> terms = new TreeSet<BytesRef>();
for (int i = 0 ; i < termBytesHash.size(); i++) {
BytesRef ref = new BytesRef();
termBytesHash.get(i, ref);
terms.add(ref);
}
final Automaton a = DaciukMihovAutomatonBuilder.build(terms);
return a;
}
}

View File

@ -16,7 +16,6 @@
*/
package org.apache.solr;
import org.apache.solr.SolrTestCaseJ4.SuppressPointFields;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.noggit.JSONUtil;
import org.noggit.ObjectBuilder;
@ -37,7 +36,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
@SuppressPointFields(bugUrl="https://issues.apache.org/jira/browse/SOLR-10939")
public class TestJoin extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@ -45,6 +43,12 @@ public class TestJoin extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeTests() throws Exception {
System.setProperty("enable.update.log", "false"); // schema12 doesn't support _version_
if (System.getProperty("solr.tests.IntegerFieldType").contains("Point")) { // all points change at the same time
// point fields need docvalues
System.setProperty("solr.tests.numeric.dv", "true");
}
initCore("solrconfig.xml","schema12.xml");
}
@ -181,12 +185,15 @@ public class TestJoin extends SolrTestCaseJ4 {
for (int qiter=0; qiter<queryIter; qiter++) {
String fromField;
String toField;
/* disable matching incompatible fields since 7.0... it doesn't work with point fields and doesn't really make sense?
if (random().nextInt(100) < 5) {
// pick random fields 5% of the time
fromField = types.get(random().nextInt(types.size())).fname;
// pick the same field 50% of the time we pick a random field (since other fields won't match anything)
toField = (random().nextInt(100) < 50) ? fromField : types.get(random().nextInt(types.size())).fname;
} else {
} else
*/
{
// otherwise, pick compatible fields that have a chance of matching indexed tokens
String[] group = compat[random().nextInt(compat.length)];
fromField = group[random().nextInt(group.length)];

View File

@ -1819,7 +1819,7 @@ public class TestJsonFacets extends SolrTestCaseHS {
* to parent/child domains preserve any existing parent/children from the original domain - eg: when q=*:*)
* </p>
*/
public void testQureyJoinBooksAndPages() throws Exception {
public void testQueryJoinBooksAndPages() throws Exception {
final Client client = Client.localClient();
@ -1854,8 +1854,8 @@ public class TestJsonFacets extends SolrTestCaseHS {
// the domains we'll be testing, initially setup for block join
final String toChildren = "join: { from:'id', to:'book_id_s' }";
final String toParents = "join: { from:'book_id_s', to:'id' }";
final String toBogusChildren = "join: { from:'id', to:'does_not_exist' }";
final String toBogusParents = "join: { from:'book_id_s', to:'does_not_exist' }";
final String toBogusChildren = "join: { from:'id', to:'does_not_exist_s' }";
final String toBogusParents = "join: { from:'book_id_s', to:'does_not_exist_s' }";
client.testJQ(params(p, "q", "*:*"
, "json.facet", "{ " +

View File

@ -32,13 +32,18 @@ public class GraphQueryTest extends SolrTestCaseJ4 {
@Test
public void testGraph() throws Exception {
// normal strings
doGraph( params("node_id","node_s", "edge_id","edge_ss") );
doGraph( params("node_id","node_s", "edge_id","edge_ss") );
doGraph( params("node_id","node_ss", "edge_id","edge_ss") );
// point based fields with docvalues
doGraph( params("node_id","node_ip", "edge_id","edge_ips") );
doGraph( params("node_id","node_lp", "edge_id","edge_lps") );
doGraph( params("node_id","node_fp", "edge_id","edge_fps") );
doGraph( params("node_id","node_dp", "edge_id","edge_dps") );
// point based fields with docvalues (single and multi-valued for the node field)
doGraph( params("node_id","node_ip", "edge_id","edge_ips") );
doGraph( params("node_id","node_ips", "edge_id","edge_ips") );
doGraph( params("node_id","node_lp", "edge_id","edge_lps") );
doGraph( params("node_id","node_lps", "edge_id","edge_lps") );
doGraph( params("node_id","node_fp", "edge_id","edge_fps") );
doGraph( params("node_id","node_fps", "edge_id","edge_fps") );
doGraph( params("node_id","node_dp", "edge_id","edge_dps") );
doGraph( params("node_id","node_dps", "edge_id","edge_dps") );
}
public void doGraph(SolrParams p) throws Exception {
@ -46,10 +51,10 @@ public class GraphQueryTest extends SolrTestCaseJ4 {
String edge_id = p.get("edge_id");
// NOTE: from/to fields are reversed from {!join}... values are looked up in the "toField" and then matched on the "fromField"
// 1->2->(3,9)->(4,5)->7
// 8->(1,2)->...
assertU(adoc("id", "doc_1", node_id, "1", edge_id, "2", "text", "foo", "title", "foo10" ));
assertU(adoc("id", "doc_2", node_id, "2", edge_id, "3", "text", "foo" ));
// 1->-2->(3,9)->(4,5)->7
// 8->(1,-2)->...
assertU(adoc("id", "doc_1", node_id, "1", edge_id, "-2", "text", "foo", "title", "foo10" ));
assertU(adoc("id", "doc_2", node_id, "-2", edge_id, "3", "text", "foo" ));
assertU(commit());
assertU(adoc("id", "doc_3", node_id, "3", edge_id, "4", edge_id, "5"));
assertU(adoc("id", "doc_4", node_id, "4" ));
@ -57,12 +62,12 @@ public class GraphQueryTest extends SolrTestCaseJ4 {
assertU(adoc("id", "doc_5", node_id, "5", edge_id, "7" ));
assertU(adoc("id", "doc_6", node_id, "6", edge_id, "3" ));
assertU(adoc("id", "doc_7", node_id, "7", edge_id, "1" ));
assertU(adoc("id", "doc_8", node_id, "8", edge_id, "1", edge_id, "2" ));
assertU(adoc("id", "doc_8", node_id, "8", edge_id, "1", edge_id, "-2" ));
assertU(adoc("id", "doc_9", node_id, "9"));
assertU(commit());
// update docs so they're in a new segment.
assertU(adoc("id", "doc_1", node_id, "1", edge_id, "2", "text", "foo"));
assertU(adoc("id", "doc_2", node_id, "2", edge_id, "3", edge_id, "9", "text", "foo11"));
assertU(adoc("id", "doc_1", node_id, "1", edge_id, "-2", "text", "foo"));
assertU(adoc("id", "doc_2", node_id, "-2", edge_id, "3", edge_id, "9", "text", "foo11"));
assertU(commit());
// a graph for testing traversal filter 10 - 11 -> (12 | 13)
assertU(adoc("id", "doc_10", node_id, "10", edge_id, "11", "title", "foo"));