diff --git a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/PartitionedFulltextReader.java b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/PartitionedFulltextReader.java index d988c7297a731..95859b4eb17f9 100644 --- a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/PartitionedFulltextReader.java +++ b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/PartitionedFulltextReader.java @@ -55,27 +55,27 @@ private PartitionedFulltextReader( List readers ) } @Override - public PrimitiveLongIterator query( String... terms ) + public PrimitiveLongIterator query( boolean matchAll, String... terms ) { - return partitionedOperation( reader -> innerQuery( reader, terms ) ); + return partitionedOperation( reader -> innerQuery( reader, matchAll, terms ) ); } @Override - public PrimitiveLongIterator fuzzyQuery( String... terms ) + public PrimitiveLongIterator fuzzyQuery( boolean matchAll, String... terms ) { - return partitionedOperation( reader -> innerFuzzyQuery( reader, terms ) ); + return partitionedOperation( reader -> innerFuzzyQuery( reader, matchAll, terms ) ); } - private PrimitiveLongIterator innerQuery( ReadOnlyFulltext reader, String... query ) + private PrimitiveLongIterator innerQuery( ReadOnlyFulltext reader, boolean matchAll, String... query ) { - return reader.query( query ); + return reader.query( matchAll, query ); } - private PrimitiveLongIterator innerFuzzyQuery( ReadOnlyFulltext reader, String... query ) + private PrimitiveLongIterator innerFuzzyQuery( ReadOnlyFulltext reader, boolean matchAll, String... query ) { - return reader.fuzzyQuery( query ); + return reader.fuzzyQuery( matchAll, query ); } public void close() diff --git a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/ReadOnlyFulltext.java b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/ReadOnlyFulltext.java index 7881c50c36c39..bb254a7f99a5a 100644 --- a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/ReadOnlyFulltext.java +++ b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/ReadOnlyFulltext.java @@ -28,18 +28,22 @@ public interface ReadOnlyFulltext extends AutoCloseable /** * Searches the fulltext index for any exact match of any of the given terms against any token in any of the indexed properties. * + * + * @param matchAll If true, only resluts that match all the given terms will be returned * @param terms The terms to query for. * @return An iterator over the matching entityIDs, ordered by lucene scoring of the match. */ - PrimitiveLongIterator query( String... terms ); + PrimitiveLongIterator query( boolean matchAll, String... terms ); /** * Searches the fulltext index for any fuzzy match of any of the given terms against any token in any of the indexed properties. * + * + * @param matchAll If true, only resluts that match all the given terms will be returned * @param terms The terms to query for. * @return An iterator over the matching entityIDs, ordered by lucene scoring of the match. */ - PrimitiveLongIterator fuzzyQuery( String... terms ); + PrimitiveLongIterator fuzzyQuery( boolean matchAll, String... terms ); @Override void close(); diff --git a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/SimpleFulltextReader.java b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/SimpleFulltextReader.java index 26e44ab190dab..a45b5b8739246 100644 --- a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/SimpleFulltextReader.java +++ b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/SimpleFulltextReader.java @@ -60,17 +60,17 @@ class SimpleFulltextReader implements ReadOnlyFulltext } @Override - public PrimitiveLongIterator query( String... terms ) + public PrimitiveLongIterator query( boolean matchAll, String... terms ) { String query = stream( terms ).map( QueryParser::escape ).collect( joining( " " ) ); - return innerQuery( query ); + return innerQuery( query, matchAll ); } @Override - public PrimitiveLongIterator fuzzyQuery( String... terms ) + public PrimitiveLongIterator fuzzyQuery( boolean matchAll, String... terms ) { String query = stream( terms ).map( QueryParser::escape ).collect( joining( "~ ", "", "~" ) ); - return innerQuery( query ); + return innerQuery( query, matchAll ); } @Override @@ -98,10 +98,17 @@ public FulltextIndexConfiguration getConfigurationDocument() throws IOException return new FulltextIndexConfiguration( indexSearcher.doc( docs.scoreDocs[0].doc ) ); } - private PrimitiveLongIterator innerQuery( String queryString ) + private PrimitiveLongIterator innerQuery( String queryString, boolean matchAll ) { MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( properties, analyzer ); - multiFieldQueryParser.setDefaultOperator( QueryParser.Operator.OR ); + if ( matchAll ) + { + multiFieldQueryParser.setDefaultOperator( QueryParser.Operator.AND ); + } + else + { + multiFieldQueryParser.setDefaultOperator( QueryParser.Operator.OR ); + } Query query; try { diff --git a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomProcedures.java b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomProcedures.java index be2eaf3e302bb..0c5a4cb889401 100644 --- a/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomProcedures.java +++ b/enterprise/fulltext-addon/src/main/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomProcedures.java @@ -76,27 +76,37 @@ public Stream bloomFulltextStatus() throws Exception @Description( "Queries the bloom index for nodes" ) @Procedure( name = "db.fulltext.bloomFulltextNodes", mode = READ ) - public Stream bloomFulltextNodes( @Name( "terms" ) List terms ) throws Exception + public Stream bloomFulltextNodes( @Name( "terms" ) List terms, @Name( "fuzzy" ) boolean fuzzy, + @Name( "Require that all terms are matched" ) boolean matchAll ) throws Exception { try ( ReadOnlyFulltext indexReader = provider.getReader( BLOOM_NODES, NODES ) ) { - return queryAsStream( terms, indexReader ); + return queryAsStream( terms, indexReader, fuzzy, matchAll ); } } @Description( "Queries the bloom index for relationships" ) @Procedure( name = "db.fulltext.bloomFulltextRelationships", mode = READ ) - public Stream bloomFulltextRelationships( @Name( "terms" ) List terms ) throws Exception + public Stream bloomFulltextRelationships( @Name( "terms" ) List terms, @Name( "fuzzy" ) boolean fuzzy, + @Name( "Require that all terms are matched" ) boolean matchAll ) throws Exception { try ( ReadOnlyFulltext indexReader = provider.getReader( BLOOM_RELATIONSHIPS, RELATIONSHIPS ) ) { - return queryAsStream( terms, indexReader ); + return queryAsStream( terms, indexReader, fuzzy, matchAll ); } } - private Stream queryAsStream( List terms, ReadOnlyFulltext indexReader ) + private Stream queryAsStream( List terms, ReadOnlyFulltext indexReader, boolean fuzzy, boolean matchAll ) { - PrimitiveLongIterator primitiveLongIterator = indexReader.fuzzyQuery( terms.toArray( new String[0] ) ); + PrimitiveLongIterator primitiveLongIterator; + if ( fuzzy ) + { + primitiveLongIterator = indexReader.fuzzyQuery( matchAll, terms.toArray( new String[0] ) ); + } + else + { + primitiveLongIterator = indexReader.query( matchAll, terms.toArray( new String[0] ) ); + } Iterator iterator = PrimitiveLongCollections.map( EntityOutput::new, primitiveLongIterator ); return StreamSupport.stream( Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED ), false ); } diff --git a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/FulltextAnalyzerTest.java b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/FulltextAnalyzerTest.java index 4964defeb869f..b2e9cd53bc0ec 100644 --- a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/FulltextAnalyzerTest.java +++ b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/FulltextAnalyzerTest.java @@ -57,9 +57,9 @@ public void shouldBeAbleToSpecifyEnglishAnalyzer() throws Exception assertExactQueryFindsNothing( reader, "and" ); assertExactQueryFindsNothing( reader, "in" ); assertExactQueryFindsNothing( reader, "the" ); - assertExactQueryFindsIds( reader, "en", id ); - assertExactQueryFindsIds( reader, "och", id ); - assertExactQueryFindsIds( reader, "ett", id ); + assertExactQueryFindsIds( reader, "en", false, id ); + assertExactQueryFindsIds( reader, "och", false, id ); + assertExactQueryFindsIds( reader, "ett", false, id ); } } } @@ -84,9 +84,9 @@ public void shouldBeAbleToSpecifySwedishAnalyzer() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( BLOOM_NODES, NODES ) ) { - assertExactQueryFindsIds( reader, "and", id ); - assertExactQueryFindsIds( reader, "in", id ); - assertExactQueryFindsIds( reader, "the", id ); + assertExactQueryFindsIds( reader, "and", false, id ); + assertExactQueryFindsIds( reader, "in", false, id ); + assertExactQueryFindsIds( reader, "the", false, id ); assertExactQueryFindsNothing( reader, "en" ); assertExactQueryFindsNothing( reader, "och" ); assertExactQueryFindsNothing( reader, "ett" ); @@ -119,9 +119,9 @@ public void shouldReindexNodesWhenAnalyzerIsChanged() throws Exception assertExactQueryFindsNothing( reader, "and" ); assertExactQueryFindsNothing( reader, "in" ); assertExactQueryFindsNothing( reader, "the" ); - assertExactQueryFindsIds( reader, "en", secondID ); - assertExactQueryFindsIds( reader, "och", secondID ); - assertExactQueryFindsIds( reader, "ett", secondID ); + assertExactQueryFindsIds( reader, "en", false, secondID ); + assertExactQueryFindsIds( reader, "och", false, secondID ); + assertExactQueryFindsIds( reader, "ett", false, secondID ); } } @@ -134,9 +134,9 @@ public void shouldReindexNodesWhenAnalyzerIsChanged() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( BLOOM_NODES, NODES ) ) { - assertExactQueryFindsIds( reader, "and", firstID ); - assertExactQueryFindsIds( reader, "in", firstID ); - assertExactQueryFindsIds( reader, "the", firstID ); + assertExactQueryFindsIds( reader, "and", false, firstID ); + assertExactQueryFindsIds( reader, "in", false, firstID ); + assertExactQueryFindsIds( reader, "the", false, firstID ); assertExactQueryFindsNothing( reader, "en" ); assertExactQueryFindsNothing( reader, "och" ); assertExactQueryFindsNothing( reader, "ett" ); diff --git a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextTestSupport.java b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextTestSupport.java index 2f06f60a151d0..cc057e3d27ac4 100644 --- a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextTestSupport.java +++ b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextTestSupport.java @@ -26,6 +26,7 @@ import java.io.File; import java.io.IOException; import java.time.Clock; +import java.util.Arrays; import org.neo4j.collection.primitive.PrimitiveLongCollections; import org.neo4j.collection.primitive.PrimitiveLongIterator; @@ -44,6 +45,7 @@ import org.neo4j.test.rule.DatabaseRule; import org.neo4j.test.rule.EmbeddedDatabaseRule; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class LuceneFulltextTestSupport @@ -110,34 +112,64 @@ protected long createRelationshipWithProperty( long firstNodeId, long secondNode protected void assertExactQueryFindsNothing( ReadOnlyFulltext reader, String query ) { - assertExactQueryFindsIds( reader, query ); + assertExactQueryFindsIds( reader, query, false ); } - protected void assertExactQueryFindsIds( ReadOnlyFulltext reader, String[] query, long... ids ) + protected void assertExactQueryFindsIds( ReadOnlyFulltext reader, String[] query, boolean matchAll, long... ids ) { - PrimitiveLongIterator result = reader.query( query ); + PrimitiveLongIterator result = reader.query( matchAll, query ); assertQueryResultsMatch( result, ids ); } - protected void assertExactQueryFindsIds( ReadOnlyFulltext reader, String query, long... ids ) + protected void assertExactQueryFindsIdsInOrder( ReadOnlyFulltext reader, String[] query, boolean matchAll, long... ids ) { - assertExactQueryFindsIds( reader, new String[]{query}, ids ); + PrimitiveLongIterator result = reader.query( matchAll, query ); + assertQueryResultsMatchInOrder( result, ids ); } - protected void assertFuzzyQueryFindsIds( ReadOnlyFulltext reader, String query, long... ids ) + protected void assertExactQueryFindsIds( ReadOnlyFulltext reader, String query, boolean matchAll, long... ids ) { - PrimitiveLongIterator result = reader.fuzzyQuery( query ); + assertExactQueryFindsIds( reader, new String[]{query}, matchAll, ids ); + } + + protected void assertFuzzyQueryFindsIds( ReadOnlyFulltext reader, String query, boolean matchAll, long... ids ) + { + assertFuzzyQueryFindsIds( reader, new String[]{query}, matchAll, ids ); + } + + protected void assertFuzzyQueryFindsIds( ReadOnlyFulltext reader, String[] query, boolean matchAll, long... ids ) + { + PrimitiveLongIterator result = reader.fuzzyQuery( matchAll, query ); assertQueryResultsMatch( result, ids ); } + protected void assertFuzzyQueryFindsIdsInOrder( ReadOnlyFulltext reader, String query, boolean matchAll, long... ids ) + { + PrimitiveLongIterator result = reader.fuzzyQuery( matchAll, query ); + assertQueryResultsMatchInOrder( result, ids ); + } + protected void assertQueryResultsMatch( PrimitiveLongIterator result, long[] ids ) { PrimitiveLongSet set = PrimitiveLongCollections.setOf( ids ); while ( result.hasNext() ) { - assertTrue( set.remove( result.next() ) ); + long next = result.next(); + assertTrue( String.format( "Result returned node id %d, expected one of %s", next, Arrays.toString( ids ) ), set.remove( next ) ); + } + assertTrue( "Number of results differ from expected", set.isEmpty() ); + } + + protected void assertQueryResultsMatchInOrder( PrimitiveLongIterator result, long[] ids ) + { + int num = 0; + while ( result.hasNext() ) + { + long next = result.next(); + assertEquals( String.format( "Result returned node id %d, expected %d", next, ids[num] ), ids[num], next ); + num++; } - assertTrue( set.isEmpty() ); + assertEquals( "Number of results differ from expected", ids.length, num ); } protected void setNodeProp( long nodeId, String value ) diff --git a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextUpdaterTest.java b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextUpdaterTest.java index d830046dc2d7b..f2c7ac359722d 100644 --- a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextUpdaterTest.java +++ b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/LuceneFulltextUpdaterTest.java @@ -56,10 +56,10 @@ public void shouldFindNodeWithString() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "hello", firstID ); - assertExactQueryFindsIds( reader, "zebra", secondID ); - assertExactQueryFindsIds( reader, "zedonk", secondID ); - assertExactQueryFindsIds( reader, "cross", secondID ); + assertExactQueryFindsIds( reader, "hello", false, firstID ); + assertExactQueryFindsIds( reader, "zebra", false, secondID ); + assertExactQueryFindsIds( reader, "zedonk", false, secondID ); + assertExactQueryFindsIds( reader, "cross", false, secondID ); } } } @@ -84,8 +84,8 @@ public void shouldFindNodeWithNumber() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "1", firstID ); - assertExactQueryFindsIds( reader, "234", secondID ); + assertExactQueryFindsIds( reader, "1", false, firstID ); + assertExactQueryFindsIds( reader, "234", false, secondID ); } } } @@ -110,8 +110,8 @@ public void shouldFindNodeWithBoolean() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "true", firstID ); - assertExactQueryFindsIds( reader, "false", secondID ); + assertExactQueryFindsIds( reader, "true", false, firstID ); + assertExactQueryFindsIds( reader, "false", false, secondID ); } } } @@ -138,9 +138,9 @@ public void shouldFindNodeWithArrays() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "live", firstID ); - assertExactQueryFindsIds( reader, "27", secondID ); - assertExactQueryFindsIds( reader, new String[]{"1", "2"}, secondID, thirdID ); + assertExactQueryFindsIds( reader, "live", false, firstID ); + assertExactQueryFindsIds( reader, "27", false, secondID ); + assertExactQueryFindsIds( reader, new String[]{"1", "2"}, false, secondID, thirdID ); } } } @@ -178,9 +178,9 @@ public void shouldRepresentPropertyChanges() throws Exception assertExactQueryFindsNothing( reader, "zebra" ); assertExactQueryFindsNothing( reader, "zedonk" ); assertExactQueryFindsNothing( reader, "cross" ); - assertExactQueryFindsIds( reader, "hahahaha", firstID ); - assertExactQueryFindsIds( reader, "farmer", secondID ); - assertExactQueryFindsIds( reader, "potato", firstID, secondID ); + assertExactQueryFindsIds( reader, "hahahaha", false, firstID ); + assertExactQueryFindsIds( reader, "farmer", false, secondID ); + assertExactQueryFindsIds( reader, "potato", false, firstID, secondID ); } } } @@ -267,7 +267,7 @@ public void shouldNotFindRemovedProperties() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "hello", secondID ); + assertExactQueryFindsIds( reader, "hello", false, secondID ); assertExactQueryFindsNothing( reader, "zebra" ); assertExactQueryFindsNothing( reader, "zedonk" ); assertExactQueryFindsNothing( reader, "cross" ); @@ -298,7 +298,7 @@ public void shouldOnlyIndexIndexedProperties() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "hello", firstID ); + assertExactQueryFindsIds( reader, "hello", false, firstID ); assertExactQueryFindsNothing( reader, "zebra" ); } } @@ -330,7 +330,7 @@ public void shouldSearchAcrossMultipleProperties() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, new String[]{"tomtar", "karl"}, firstID, secondID, thirdID ); + assertExactQueryFindsIds( reader, new String[]{"tomtar", "karl"}, false, firstID, secondID, thirdID ); } } } @@ -367,7 +367,7 @@ public void shouldOrderResultsBasedOnRelevance() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, new String[]{"Tom", "Hanks"}, firstID, secondID, thirdID, fourthID ); + assertExactQueryFindsIdsInOrder( reader, new String[]{"Tom", "Hanks"}, false, fourthID, thirdID, firstID, secondID ); } } } @@ -401,15 +401,15 @@ public void shouldDifferentiateNodesAndRelationships() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "hello", firstNodeID ); - assertExactQueryFindsIds( reader, "zebra", secondNodeID ); + assertExactQueryFindsIds( reader, "hello", false, firstNodeID ); + assertExactQueryFindsIds( reader, "zebra", false, secondNodeID ); assertExactQueryFindsNothing( reader, "different" ); } try ( ReadOnlyFulltext reader = provider.getReader( "relationships", RELATIONSHIPS ) ) { - assertExactQueryFindsIds( reader, "hello", firstRelID ); + assertExactQueryFindsIds( reader, "hello", false, firstRelID ); assertExactQueryFindsNothing( reader, "zebra" ); - assertExactQueryFindsIds( reader, "different", secondRelID ); + assertExactQueryFindsIds( reader, "different", false, secondRelID ); } } } @@ -436,10 +436,10 @@ public void fuzzyQueryShouldBeFuzzy() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertFuzzyQueryFindsIds( reader, "hella", firstID ); - assertFuzzyQueryFindsIds( reader, "zebre", secondID ); - assertFuzzyQueryFindsIds( reader, "zedink", secondID ); - assertFuzzyQueryFindsIds( reader, "cruss", secondID ); + assertFuzzyQueryFindsIds( reader, "hella", false, firstID ); + assertFuzzyQueryFindsIds( reader, "zebre", false, secondID ); + assertFuzzyQueryFindsIds( reader, "zedink", false, secondID ); + assertFuzzyQueryFindsIds( reader, "cruss", false, secondID ); assertExactQueryFindsNothing( reader, "hella" ); assertExactQueryFindsNothing( reader, "zebre" ); assertExactQueryFindsNothing( reader, "zedink" ); @@ -472,7 +472,7 @@ public void fuzzyQueryShouldReturnExactMatchesFirst() throws Exception try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertFuzzyQueryFindsIds( reader, "zebra", firstID, secondID, thirdID, fourthID ); + assertFuzzyQueryFindsIdsInOrder( reader, "zebra", true, thirdID, secondID, fourthID, firstID ); } } } @@ -543,8 +543,8 @@ public void shouldPopulateIndexWithExistingNodesAndRelationships() throws Except try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "hello", firstNodeID ); - assertExactQueryFindsIds( reader, "string", secondNodeID ); + assertExactQueryFindsIds( reader, "hello", false, firstNodeID ); + assertExactQueryFindsIds( reader, "string", false, secondNodeID ); assertExactQueryFindsNothing( reader, "goodbye" ); assertExactQueryFindsNothing( reader, "different" ); } @@ -552,8 +552,8 @@ public void shouldPopulateIndexWithExistingNodesAndRelationships() throws Except { assertExactQueryFindsNothing( reader, "hello" ); assertExactQueryFindsNothing( reader, "string" ); - assertExactQueryFindsIds( reader, "goodbye", firstRelID ); - assertExactQueryFindsIds( reader, "different", secondRelID ); + assertExactQueryFindsIds( reader, "goodbye", false, firstRelID ); + assertExactQueryFindsIds( reader, "different", false, secondRelID ); } } } @@ -581,10 +581,92 @@ public void shouldReturnMatchesThatContainLuceneSyntaxCharacters() throws Except try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) { - assertExactQueryFindsIds( reader, "Hello" + elm, nodeId ); - assertExactQueryFindsIds( reader, elm + "today", nodeId ); + assertExactQueryFindsIds( reader, "Hello" + elm, false, nodeId ); + assertExactQueryFindsIds( reader, elm + "today", false, nodeId ); } } } } + + @Test + public void exactMatchAllShouldOnlyReturnStuffThatMatchesAll() throws Exception + { + try ( FulltextProvider provider = createProvider() ) + { + fulltextFactory.createFulltextIndex( "nodes", NODES, Arrays.asList( "first", "last" ) ); + provider.init(); + + long firstID; + long secondID; + long thirdID; + long fourthID; + long fifthID; + try ( Transaction tx = db.beginTx() ) + { + firstID = db.createNode().getId(); + secondID = db.createNode().getId(); + thirdID = db.createNode().getId(); + fourthID = db.createNode().getId(); + fifthID = db.createNode().getId(); + setNodeProp( firstID, "first", "Full" ); + setNodeProp( firstID, "last", "Hanks" ); + setNodeProp( secondID, "first", "Tom" ); + setNodeProp( secondID, "last", "Hunk" ); + setNodeProp( thirdID, "first", "Tom" ); + setNodeProp( thirdID, "last", "Hanks" ); + setNodeProp( fourthID, "first", "Tom Hanks" ); + setNodeProp( fourthID, "last", "Tom Hanks" ); + setNodeProp( fifthID, "last", "Tom Hanks" ); + setNodeProp( fifthID, "first", "Morgan" ); + + tx.success(); + } + + try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) + { + assertExactQueryFindsIds( reader, new String[]{"Tom", "Hanks"}, true, thirdID, fourthID, fifthID ); + } + } + } + + @Test + public void fuzzyMatchAllShouldOnlyReturnStuffThatKindaMatchesAll() throws Exception + { + try ( FulltextProvider provider = createProvider() ) + { + fulltextFactory.createFulltextIndex( "nodes", NODES, Arrays.asList( "first", "last" ) ); + provider.init(); + + long firstID; + long secondID; + long thirdID; + long fourthID; + long fifthID; + try ( Transaction tx = db.beginTx() ) + { + firstID = db.createNode().getId(); + secondID = db.createNode().getId(); + thirdID = db.createNode().getId(); + fourthID = db.createNode().getId(); + fifthID = db.createNode().getId(); + setNodeProp( firstID, "first", "Christian" ); + setNodeProp( firstID, "last", "Hanks" ); + setNodeProp( secondID, "first", "Tom" ); + setNodeProp( secondID, "last", "Hungarian" ); + setNodeProp( thirdID, "first", "Tom" ); + setNodeProp( thirdID, "last", "Hunk" ); + setNodeProp( fourthID, "first", "Tim" ); + setNodeProp( fourthID, "last", "Hanks" ); + setNodeProp( fifthID, "last", "Tom Hanks" ); + setNodeProp( fifthID, "first", "Morgan" ); + + tx.success(); + } + + try ( ReadOnlyFulltext reader = provider.getReader( "nodes", NODES ) ) + { + assertFuzzyQueryFindsIds( reader, new String[]{"Tom", "Hanks"}, true, thirdID, fourthID, fifthID ); + } + } + } } diff --git a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomIT.java b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomIT.java index 572c74b4efcf4..8aae371e493df 100644 --- a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomIT.java +++ b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/integrations/bloom/BloomIT.java @@ -62,8 +62,8 @@ public class BloomIT { - private static final String NODES = "CALL db.fulltext.bloomFulltextNodes([\"%s\"])"; - private static final String RELS = "CALL db.fulltext.bloomFulltextRelationships([\"%s\"])"; + private static final String NODES = "CALL db.fulltext.bloomFulltextNodes([\"%s\"], %b, %b)"; + private static final String RELS = "CALL db.fulltext.bloomFulltextRelationships([\"%s\"], %b, %b)"; private static final String ENTITYID = "entityid"; @Rule @@ -107,15 +107,56 @@ public void shouldPopulateAndQueryIndexes() throws Exception transaction.success(); } - Result result = db.execute( String.format( NODES, "integration" ) ); + Result result = db.execute( String.format( NODES, "integration", true, false ) ); assertEquals( 0L, result.next().get( ENTITYID ) ); assertEquals( 1L, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); - result = db.execute( String.format( RELS, "relate" ) ); + result = db.execute( String.format( RELS, "relate", true, false ) ); assertEquals( 0L, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); } + @Test + public void exactQueryShouldBeExact() throws Exception + { + builder.setConfig( bloom_indexed_properties, "prop" ); + db = builder.newGraphDatabase(); + try ( Transaction transaction = db.beginTx() ) + { + Node node1 = db.createNode(); + node1.setProperty( "prop", "This is a integration test." ); + Node node2 = db.createNode(); + node2.setProperty( "prop", "This is a related integration test" ); + transaction.success(); + } + + Result result = db.execute( String.format( NODES, "integration", false, false ) ); + assertEquals( 0L, result.next().get( ENTITYID ) ); + assertEquals( 1L, result.next().get( ENTITYID ) ); + assertFalse( result.hasNext() ); + result = db.execute( String.format( NODES, "integratiun", false, false ) ); + assertFalse( result.hasNext() ); + } + + @Test + public void matchAllQueryShouldMatchAll() throws Exception + { + builder.setConfig( bloom_indexed_properties, "prop" ); + db = builder.newGraphDatabase(); + try ( Transaction transaction = db.beginTx() ) + { + Node node1 = db.createNode(); + node1.setProperty( "prop", "This is a integration test." ); + Node node2 = db.createNode(); + node2.setProperty( "prop", "This is a related integration test" ); + transaction.success(); + } + + Result result = db.execute( String.format( NODES, "integration, related", false, true ) ); + assertEquals( 1L, result.next().get( ENTITYID ) ); + assertFalse( result.hasNext() ); + } + @Test public void shouldBeAbleToConfigureAnalyzer() throws Exception { @@ -131,15 +172,15 @@ public void shouldBeAbleToConfigureAnalyzer() throws Exception transaction.success(); } - Result result = db.execute( String.format( NODES, "is" ) ); + Result result = db.execute( String.format( NODES, "is", true, false ) ); assertEquals( 1L, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); - result = db.execute( String.format( NODES, "a" ) ); + result = db.execute( String.format( NODES, "a", true, false ) ); assertEquals( 1L, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); - result = db.execute( String.format( NODES, "det" ) ); + result = db.execute( String.format( NODES, "det", true, false ) ); assertFalse( result.hasNext() ); - result = db.execute( String.format( NODES, "en" ) ); + result = db.execute( String.format( NODES, "en", true, false ) ); assertFalse( result.hasNext() ); } @@ -157,7 +198,7 @@ public void shouldPopulateIndexWithExistingDataOnIndexCreate() throws Exception nodeId = node.getId(); tx.success(); } - Result result = db.execute( String.format( NODES, "Roskildevej" ) ); + Result result = db.execute( String.format( NODES, "Roskildevej", true, false ) ); assertFalse( result.hasNext() ); db.shutdown(); @@ -167,7 +208,7 @@ public void shouldPopulateIndexWithExistingDataOnIndexCreate() throws Exception db.execute( "CALL db.fulltext.bloomAwaitPopulation" ).close(); - result = db.execute( String.format( NODES, "Roskildevej" ) ); + result = db.execute( String.format( NODES, "Roskildevej", true, false ) ); assertTrue( result.hasNext() ); assertEquals( nodeId, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); @@ -190,7 +231,7 @@ public void startupPopulationShouldNotCauseDuplicates() throws Exception // Verify it's indexed exactly once db.execute( "CALL db.fulltext.bloomAwaitPopulation" ).close(); - Result result = db.execute( String.format( NODES, "Jyllingevej" ) ); + Result result = db.execute( String.format( NODES, "Jyllingevej", true, false ) ); assertTrue( result.hasNext() ); assertEquals( nodeId, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); @@ -200,7 +241,7 @@ public void startupPopulationShouldNotCauseDuplicates() throws Exception // Verify it's STILL indexed exactly once db.execute( "CALL db.fulltext.bloomAwaitPopulation" ).close(); - result = db.execute( String.format( NODES, "Jyllingevej" ) ); + result = db.execute( String.format( NODES, "Jyllingevej", true, false ) ); assertTrue( result.hasNext() ); assertEquals( nodeId, result.next().get( ENTITYID ) ); assertFalse( result.hasNext() ); @@ -238,7 +279,7 @@ public void staleDataFromEntityDeleteShouldNotBeAccessibleAfterConfigurationChan // Verify that the node is no longer indexed db.execute( "CALL db.fulltext.bloomAwaitPopulation" ).close(); - Result result = db.execute( String.format( NODES, "Esplanaden" ) ); + Result result = db.execute( String.format( NODES, "Esplanaden", true, false ) ); assertFalse( result.hasNext() ); result.close(); } @@ -275,7 +316,7 @@ public void staleDataFromPropertyRemovalShouldNotBeAccessibleAfterConfigurationC // Verify that the node is no longer indexed db.execute( "CALL db.fulltext.bloomAwaitPopulation" ).close(); - Result result = db.execute( String.format( NODES, "Esplanaden" ) ); + Result result = db.execute( String.format( NODES, "Esplanaden", true, false ) ); assertFalse( result.hasNext() ); result.close(); } @@ -294,7 +335,7 @@ public void updatesAreAvailableToConcurrentReadTransactions() throws Exception try ( Transaction ignore = db.beginTx() ) { - try ( Result result = db.execute( String.format( NODES, "Langelinie" ) ) ) + try ( Result result = db.execute( String.format( NODES, "Langelinie", true, false ) ) ) { assertThat( Iterators.count( result ), is( 1L ) ); } @@ -310,7 +351,7 @@ public void updatesAreAvailableToConcurrentReadTransactions() throws Exception th.start(); th.join(); - try ( Result result = db.execute( String.format( NODES, "Langelinie" ) ) ) + try ( Result result = db.execute( String.format( NODES, "Langelinie", true, false ) ) ) { assertThat( Iterators.count( result ), is( 2L ) ); } @@ -373,11 +414,11 @@ public void shouldReindexNodesWhenAnalyzerIsChanged() throws Exception } try ( Transaction ignore = db.beginTx() ) { - try ( Result result = db.execute( String.format( NODES, "and" ) ) ) + try ( Result result = db.execute( String.format( NODES, "and", false, false ) ) ) { assertThat( Iterators.count( result ), is( 0L ) ); } - try ( Result result = db.execute( String.format( NODES, "ett" ) ) ) + try ( Result result = db.execute( String.format( NODES, "ett", false, false ) ) ) { assertThat( Iterators.count( result ), is( 1L ) ); } @@ -390,11 +431,11 @@ public void shouldReindexNodesWhenAnalyzerIsChanged() throws Exception try ( Transaction ignore = db.beginTx() ) { - try ( Result result = db.execute( String.format( NODES, "and" ) ) ) + try ( Result result = db.execute( String.format( NODES, "and", false, false ) ) ) { assertThat( Iterators.count( result ), is( 1L ) ); } - try ( Result result = db.execute( String.format( NODES, "ett" ) ) ) + try ( Result result = db.execute( String.format( NODES, "ett", false, false ) ) ) { assertThat( Iterators.count( result ), is( 0L ) ); } @@ -435,11 +476,11 @@ public void shouldReindexAfterBeingTemporarilyDisabled() throws Exception // Now we should be able to find the node that was added while the index was disabled. try ( Transaction ignore = db.beginTx() ) { - try ( Result result = db.execute( String.format( NODES, "hello" ) ) ) + try ( Result result = db.execute( String.format( NODES, "hello", false, false ) ) ) { assertThat( Iterators.count( result ), is( 1L ) ); } - try ( Result result = db.execute( String.format( NODES, "tomte" ) ) ) + try ( Result result = db.execute( String.format( NODES, "tomte", false, false ) ) ) { assertThat( Iterators.count( result ), is( 1L ) ); }