Skip to content

Commit

Permalink
Apply filter on index lookups for geometry
Browse files Browse the repository at this point in the history
Spatial index needs post processing because it will have potential false
positive hits due to multi dimensional to one dimensional transformation
  • Loading branch information
OliviaYtterbrink committed Feb 14, 2018
1 parent 559047f commit 5435c43
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 16 deletions.
Expand Up @@ -59,18 +59,18 @@ public static PrimitiveLongIterator exactIndexMatches( PropertyAccessor accessor
return indexedNodeIds; return indexedNodeIds;
} }


IndexQuery[] numericPredicates = IndexQuery[] filteredPredicates =
Arrays.stream( predicates ) Arrays.stream( predicates )
.filter( LookupFilter::isNumericPredicate ) .filter( LookupFilter::isNumericOrGeometricPredicate )
.toArray( IndexQuery[]::new ); .toArray( IndexQuery[]::new );


if ( numericPredicates.length > 0 ) if ( filteredPredicates.length > 0 )
{ {
LongPredicate combinedPredicate = nodeId -> LongPredicate combinedPredicate = nodeId ->
{ {
try try
{ {
for ( IndexQuery predicate : numericPredicates ) for ( IndexQuery predicate : filteredPredicates )
{ {
int propertyKeyId = predicate.propertyKeyId(); int propertyKeyId = predicate.propertyKeyId();
Value value = accessor.getPropertyValue( nodeId, propertyKeyId ); Value value = accessor.getPropertyValue( nodeId, propertyKeyId );
Expand Down Expand Up @@ -110,19 +110,19 @@ public static PrimitiveLongResourceIterator exactIndexMatches( EntityOperations
return indexedNodeIds; return indexedNodeIds;
} }


IndexQuery[] numericPredicates = IndexQuery[] filteredPredicates =
Arrays.stream( predicates ) Arrays.stream( predicates )
.filter( LookupFilter::isNumericPredicate ) .filter( LookupFilter::isNumericOrGeometricPredicate )
.toArray( IndexQuery[]::new ); .toArray( IndexQuery[]::new );


if ( numericPredicates.length > 0 ) if ( filteredPredicates.length > 0 )
{ {
LongPredicate combinedPredicate = nodeId -> LongPredicate combinedPredicate = nodeId ->
{ {
try ( Cursor<NodeItem> node = operations.nodeCursorById( state, nodeId ) ) try ( Cursor<NodeItem> node = operations.nodeCursorById( state, nodeId ) )
{ {
NodeItem nodeItem = node.get(); NodeItem nodeItem = node.get();
for ( IndexQuery predicate : numericPredicates ) for ( IndexQuery predicate : filteredPredicates )
{ {
int propertyKeyId = predicate.propertyKeyId(); int propertyKeyId = predicate.propertyKeyId();
Value value = operations.nodeGetProperty( state, nodeItem, propertyKeyId ); Value value = operations.nodeGetProperty( state, nodeItem, propertyKeyId );
Expand All @@ -144,26 +144,26 @@ public static PrimitiveLongResourceIterator exactIndexMatches( EntityOperations
return indexedNodeIds; return indexedNodeIds;
} }


private static boolean isNumericPredicate( IndexQuery predicate ) private static boolean isNumericOrGeometricPredicate( IndexQuery predicate )
{ {


if ( predicate.type() == IndexQuery.IndexQueryType.exact ) if ( predicate.type() == IndexQuery.IndexQueryType.exact )
{ {
IndexQuery.ExactPredicate exactPredicate = (IndexQuery.ExactPredicate) predicate; IndexQuery.ExactPredicate exactPredicate = (IndexQuery.ExactPredicate) predicate;
if ( isNumberOrArray( exactPredicate.value() ) ) if ( isNumberGeometryOrArray( exactPredicate.value() ) )
{ {
return true; return true;
} }
} }
else if ( predicate.type() == IndexQuery.IndexQueryType.rangeNumeric ) else if ( predicate.type() == IndexQuery.IndexQueryType.rangeNumeric || predicate.type() == IndexQuery.IndexQueryType.rangeGeometric )
{ {
return true; return true;
} }
return false; return false;
} }


private static boolean isNumberOrArray( Value value ) private static boolean isNumberGeometryOrArray( Value value )
{ {
return Values.isNumberValue( value ) || Values.isArrayValue( value ); return Values.isNumberValue( value ) || Values.isGeometryValue( value ) || Values.isArrayValue( value );
} }
} }
Expand Up @@ -196,7 +196,7 @@ private void initFromForRange( NumberRangePredicate rangePredicate, KEY treeKeyF
@Override @Override
public boolean hasFullNumberPrecision( IndexQuery... predicates ) public boolean hasFullNumberPrecision( IndexQuery... predicates )
{ {
return true; return false;
} }


private void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, KEY treeKeyFrom, KEY treeKeyTo ) private void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, KEY treeKeyFrom, KEY treeKeyTo )
Expand Down
Expand Up @@ -350,20 +350,44 @@ class SpatialFunctionsAcceptanceTest extends ExecutionEngineFunSuite with Cypher
graph.execute("MATCH (p:Place) SET p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point") graph.execute("MATCH (p:Place) SET p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point")


// When // When
val result = executeWith(expectedToSucceed - Configs.AllRulePlanners, "MATCH (p:Place) WHERE p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point", val result = executeWith(expectedToSucceed - Configs.AllRulePlanners,
"MATCH (p:Place) WHERE p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point",
planComparisonStrategy = ComparePlansWithAssertion({ plan => planComparisonStrategy = ComparePlansWithAssertion({ plan =>
plan should useOperatorWithText("Projection", "point") plan should useOperatorWithText("Projection", "point")
plan should useOperatorWithText("NodeIndexSeek", ":Place(location)") plan should useOperatorWithText("NodeIndexSeek", ":Place(location)")
}, expectPlansToFail = Configs.AbsolutelyAll - Configs.Version3_4)) }, expectPlansToFail = Configs.AbsolutelyAll - Configs.Version3_4))


println(result.executionPlanDescription())
// Then // Then
val point = result.columnAs("point").toList.head.asInstanceOf[Point] val point = result.columnAs("point").toList.head.asInstanceOf[Point]
point should equal(Values.pointValue(CoordinateReferenceSystem.WGS84, 12.78, 56.7)) point should equal(Values.pointValue(CoordinateReferenceSystem.WGS84, 12.78, 56.7))
// And CRS names should equal // And CRS names should equal
point.getCRS.getHref should equal("http://spatialreference.org/ref/epsg/4326/") point.getCRS.getHref should equal("http://spatialreference.org/ref/epsg/4326/")
} }


test("with multiple indexed points only exact match should be returned") {
// Given
graph.inTx {
graph.schema().indexFor(Label.label("Place")).on("location").create()
}
graph.inTx {
graph.schema().awaitIndexesOnline(5, TimeUnit.SECONDS)
}
createLabeledNode("Place")
graph.execute("MATCH (p:Place) SET p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point")
graph.execute("CREATE (p:Place) SET p.location = point({latitude: 50.7, longitude: 12.78, crs: 'WGS-84'})")

// When
val result = executeWith(Configs.Version3_4 - Configs.Compiled - Configs.AllRulePlanners,
"MATCH (p:Place) WHERE p.location = point({latitude: 56.7, longitude: 12.78, crs: 'WGS-84'}) RETURN p.location as point",
planComparisonStrategy = ComparePlansWithAssertion({ plan =>
plan should useOperatorWithText("Projection", "point")
plan should useOperatorWithText("NodeIndexSeek", ":Place(location)")
}, expectPlansToFail = Configs.AbsolutelyAll - Configs.Version3_4))

// Then
result.toList should equal(List(Map("point" -> Values.pointValue(CoordinateReferenceSystem.WGS84, 12.78, 56.7))))
}

test("array of points should be assignable to node property") { test("array of points should be assignable to node property") {
// Given // Given
createLabeledNode("Place") createLabeledNode("Place")
Expand Down

0 comments on commit 5435c43

Please sign in to comment.