/** * Copyright (c) 2002-2014 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.index.impl.lucene; import org.apache.lucene.index.Term; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.similarities.DefaultSimilarity; import org.junit.Ignore; import org.junit.Test; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.graphdb.index.IndexManager; import org.neo4j.graphdb.index.RelationshipIndex; import org.neo4j.graphdb.index.UniqueFactory; import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.index.lucene.QueryContext; import org.neo4j.index.lucene.ValueContext; import org.neo4j.kernel.InternalAbstractGraphDatabase; import org.neo4j.test.ImpermanentGraphDatabase; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import static org.apache.lucene.search.NumericRangeQuery.newIntRange; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.index.Neo4jTestCase.assertContains; import static org.neo4j.index.Neo4jTestCase.assertContainsInOrder; import static org.neo4j.index.impl.lucene.Contains.contains; import static org.neo4j.index.impl.lucene.IsEmpty.isEmpty; import static org.neo4j.index.lucene.QueryContext.numericRange; import static org.neo4j.index.lucene.ValueContext.numeric; public class TestLuceneIndex extends AbstractLuceneIndexTest { @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void makeSureAdditionsCanBeRead( Index<T> index, EntityCreator<T> entityCreator ) { String key = "name"; String value = "Mattias"; assertThat( index.get( key, value ).getSingle(), is( nullValue() ) ); assertThat( index.get( key, value ), isEmpty() ); assertThat( index.query( key, "*" ), isEmpty() ); T entity1 = entityCreator.create(); T entity2 = entityCreator.create(); index.add( entity1, key, value ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, value ), contains( entity1 ) ); assertThat( index.query( key, "*" ), contains( entity1 ) ); assertThat( index.get( key, value ), contains( entity1 ) ); restartTx(); } index.add( entity2, key, value ); assertThat( index.get( key, value ), contains( entity1, entity2 ) ); restartTx(); assertThat( index.get( key, value ), contains( entity1, entity2 ) ); index.delete(); } @Test public void makeSureYouGetLatestTxModificationsInQueryByDefault() { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); assertThat( index.query( "key:value" ), contains( node ) ); } @Test public void makeSureLuceneIndexesReportAsWriteable() { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); assertTrue( index.isWriteable() ); } @Test public void testStartupInExistingDirectory() { InternalAbstractGraphDatabase graphDatabase = new ImpermanentGraphDatabase(); Index<Node> index = graphDatabase.index().forNodes("nodes"); assertNotNull(index); } @Test public void makeSureAdditionsCanBeReadNodeExact() { makeSureAdditionsCanBeRead( nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ), NODE_CREATOR ); } @Test public void makeSureAdditionsCanBeReadNodeFulltext() { makeSureAdditionsCanBeRead( nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ), NODE_CREATOR ); } @Test public void makeSureAdditionsCanBeReadRelationshipExact() { makeSureAdditionsCanBeRead( relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ), RELATIONSHIP_CREATOR ); } @Test public void makeSureAdditionsCanBeReadRelationshipFulltext() { makeSureAdditionsCanBeRead( relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ), RELATIONSHIP_CREATOR ); } @Test public void makeSureAdditionsCanBeRemovedInSameTx() { makeSureAdditionsCanBeRemoved( false ); } @Test @Ignore public void makeSureYouCanAskIfAnIndexExistsOrNot() { String name = currentIndexName(); assertFalse( graphDb.index().existsForNodes( name ) ); graphDb.index().forNodes( name ); assertTrue( graphDb.index().existsForNodes( name ) ); assertFalse( graphDb.index().existsForRelationships( name ) ); graphDb.index().forRelationships( name ); assertTrue( graphDb.index().existsForRelationships( name ) ); } private void makeSureAdditionsCanBeRemoved( boolean restartTx ) { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "name"; String value = "Mattias"; assertNull( index.get( key, value ).getSingle() ); Node node = graphDb.createNode(); index.add( node, key, value ); if ( restartTx ) { restartTx(); } assertEquals( node, index.get( key, value ).getSingle() ); index.remove( node, key, value ); assertNull( index.get( key, value ).getSingle() ); restartTx(); assertNull( index.get( key, value ).getSingle() ); node.delete(); index.delete(); } @Test public void makeSureAdditionsCanBeRemoved() { makeSureAdditionsCanBeRemoved( true ); } private void makeSureSomeAdditionsCanBeRemoved( boolean restartTx ) { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key1 = "name"; String key2 = "title"; String value1 = "Mattias"; assertNull( index.get( key1, value1 ).getSingle() ); assertNull( index.get( key2, value1 ).getSingle() ); Node node = graphDb.createNode(); Node node2 = graphDb.createNode(); index.add( node, key1, value1 ); index.add( node, key2, value1 ); index.add( node2, key1, value1 ); if ( restartTx ) { restartTx(); } index.remove( node, key1, value1 ); index.remove( node, key2, value1 ); assertEquals( node2, index.get( key1, value1 ).getSingle() ); assertNull( index.get( key2, value1 ).getSingle() ); assertEquals( node2, index.get( key1, value1 ).getSingle() ); assertNull( index.get( key2, value1 ).getSingle() ); node.delete(); index.delete(); } @Test public void makeSureSomeAdditionsCanBeRemovedInSameTx() { makeSureSomeAdditionsCanBeRemoved( false ); } @Test public void makeSureSomeAdditionsCanBeRemoved() { makeSureSomeAdditionsCanBeRemoved( true ); } @Test public void makeSureThereCanBeMoreThanOneValueForAKeyAndEntity() { makeSureThereCanBeMoreThanOneValueForAKeyAndEntity( false ); } @Test public void makeSureThereCanBeMoreThanOneValueForAKeyAndEntitySameTx() { makeSureThereCanBeMoreThanOneValueForAKeyAndEntity( true ); } private void makeSureThereCanBeMoreThanOneValueForAKeyAndEntity( boolean restartTx ) { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "name"; String value1 = "Lucene"; String value2 = "Index"; String value3 = "Rules"; assertThat( index.query( key, "*" ), isEmpty() ); Node node = graphDb.createNode(); index.add( node, key, value1 ); index.add( node, key, value2 ); if ( restartTx ) { restartTx(); } index.add( node, key, value3 ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, value1 ), contains( node ) ); assertThat( index.get( key, value2 ), contains( node ) ); assertThat( index.get( key, value3 ), contains( node ) ); assertThat( index.get( key, "whatever" ), isEmpty() ); restartTx(); } index.delete(); } @Test public void shouldNotGetLatestTxModificationsWhenChoosingSpeedQueries() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); QueryContext queryContext = new QueryContext( "value" ).tradeCorrectnessForSpeed(); assertThat( index.query( "key", queryContext ), isEmpty() ); assertThat( index.query( "key", "value" ), contains( node ) ); } @Test public void makeSureArrayValuesAreSupported() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "name"; String value1 = "Lucene"; String value2 = "Index"; String value3 = "Rules"; assertThat( index.query( key, "*" ), isEmpty() ); Node node = graphDb.createNode(); index.add( node, key, new String[]{value1, value2, value3} ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, value1 ), contains( node ) ); assertThat( index.get( key, value2 ), contains( node ) ); assertThat( index.get( key, value3 ), contains( node ) ); assertThat( index.get( key, "whatever" ), isEmpty() ); restartTx(); } index.remove( node, key, new String[]{value2, value3} ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, value1 ), contains( node ) ); assertThat( index.get( key, value2 ), isEmpty() ); assertThat( index.get( key, value3 ), isEmpty() ); restartTx(); } index.delete(); } @Test public void makeSureWildcardQueriesCanBeAsked() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "name"; String value1 = "neo4j"; String value2 = "nescafe"; Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); index.add( node1, key, value1 ); index.add( node2, key, value2 ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( key, "neo*" ), contains( node1 ) ); assertThat( index.query( key, "n?o4j" ), contains( node1 ) ); assertThat( index.query( key, "ne*" ), contains( node1, node2 ) ); assertThat( index.query( key + ":neo4j" ), contains( node1 ) ); assertThat( index.query( key + ":neo*" ), contains( node1 ) ); assertThat( index.query( key + ":n?o4j" ), contains( node1 ) ); assertThat( index.query( key + ":ne*" ), contains( node1, node2 ) ); restartTx(); } index.delete(); } @Test public void makeSureCompositeQueriesCanBeAsked() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node neo = graphDb.createNode(); Node trinity = graphDb.createNode(); index.add( neo, "username", "neo@matrix" ); index.add( neo, "sex", "male" ); index.add( trinity, "username", "trinity@matrix" ); index.add( trinity, "sex", "female" ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( "username:*@matrix AND sex:male" ), contains( neo ) ); assertThat( index.query( new QueryContext( "username:*@matrix sex:male" ).defaultOperator( QueryParser.Operator.AND ) ), contains( neo ) ); assertThat( index.query( "username:*@matrix OR sex:male" ), contains( neo, trinity ) ); assertThat( index.query( new QueryContext( "username:*@matrix sex:male" ).defaultOperator( QueryParser.Operator.OR ) ), contains( neo, trinity ) ); restartTx(); } index.delete(); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void doSomeRandomUseCaseTestingWithExactIndex( Index<T> index, EntityCreator<T> creator ) { String name = "name"; String mattias = "Mattias Persson"; String title = "title"; String hacker = "Hacker"; assertThat( index.get( name, mattias ), isEmpty() ); T entity1 = creator.create(); T entity2 = creator.create(); assertNull( index.get( name, mattias ).getSingle() ); index.add( entity1, name, mattias ); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertContains( index.query( name, "\"" + mattias + "\"" ), entity1 ); assertContains( index.query( "name:\"" + mattias + "\"" ), entity1 ); assertEquals( entity1, index.get( name, mattias ).getSingle() ); assertContains( index.query( "name", "Mattias*" ), entity1 ); commitTx(); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertThat( index.query( name, "\"" + mattias + "\"" ), contains( entity1 ) ); assertThat( index.query( "name:\"" + mattias + "\"" ), contains( entity1 ) ); assertEquals( entity1, index.get( name, mattias ).getSingle() ); assertThat( index.query( "name", "Mattias*" ), contains( entity1 ) ); beginTx(); index.add( entity2, title, hacker ); index.add( entity1, title, hacker ); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertThat( index.get( title, hacker ), contains( entity1, entity2 ) ); assertContains( index.query( "name:\"" + mattias + "\" OR title:\"" + hacker + "\"" ), entity1, entity2 ); commitTx(); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertThat( index.get( title, hacker ), contains( entity1, entity2 ) ); assertThat( index.query( "name:\"" + mattias + "\" OR title:\"" + hacker + "\"" ), contains( entity1, entity2 ) ); assertThat( index.query( "name:\"" + mattias + "\" AND title:\"" + hacker + "\"" ), contains( entity1 ) ); beginTx(); index.remove( entity2, title, hacker ); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertThat( index.get( title, hacker ), contains( entity1 ) ); assertContains( index.query( "name:\"" + mattias + "\" OR title:\"" + hacker + "\"" ), entity1 ); commitTx(); assertThat( index.get( name, mattias ), contains( entity1 ) ); assertThat( index.get( title, hacker ), contains( entity1 ) ); assertThat( index.query( "name:\"" + mattias + "\" OR title:\"" + hacker + "\"" ), contains( entity1 ) ); beginTx(); index.remove( entity1, title, hacker ); index.remove( entity1, name, mattias ); index.delete(); commitTx(); } @Test public void doSomeRandomUseCaseTestingWithExactNodeIndex() { doSomeRandomUseCaseTestingWithExactIndex( nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ), NODE_CREATOR ); } @Test public void doSomeRandomUseCaseTestingWithExactRelationshipIndex() { doSomeRandomUseCaseTestingWithExactIndex( relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ), RELATIONSHIP_CREATOR ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void doSomeRandomTestingWithFulltextIndex( Index<T> index, EntityCreator<T> creator ) { T entity1 = creator.create(); T entity2 = creator.create(); String key = "name"; index.add( entity1, key, "The quick brown fox" ); index.add( entity2, key, "brown fox jumped over" ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, "The quick brown fox" ), contains( entity1 ) ); assertThat( index.get( key, "brown fox jumped over" ), contains( entity2 ) ); assertThat( index.query( key, "quick" ), contains( entity1 ) ); assertThat( index.query( key, "brown" ), contains( entity1, entity2 ) ); assertThat( index.query( key, "quick OR jumped" ), contains( entity1, entity2 ) ); assertThat( index.query( key, "brown AND fox" ), contains( entity1, entity2 ) ); restartTx(); } index.delete(); } @Test public void doSomeRandomTestingWithNodeFulltextInde() { doSomeRandomTestingWithFulltextIndex( nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ), NODE_CREATOR ); } @Test public void doSomeRandomTestingWithRelationshipFulltextInde() { doSomeRandomTestingWithFulltextIndex( relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ), RELATIONSHIP_CREATOR ); } @Test public void testNodeLocalRelationshipIndex() { RelationshipIndex index = relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ); RelationshipType type = DynamicRelationshipType.withName( "YO" ); Node startNode = graphDb.createNode(); Node endNode1 = graphDb.createNode(); Node endNode2 = graphDb.createNode(); Relationship rel1 = startNode.createRelationshipTo( endNode1, type ); Relationship rel2 = startNode.createRelationshipTo( endNode2, type ); index.add( rel1, "name", "something" ); index.add( rel2, "name", "something" ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( "name:something" ), contains( rel1, rel2 ) ); assertThat( index.query( "name:something", null, endNode1 ), contains( rel1 ) ); assertThat( index.query( "name:something", startNode, endNode2 ), contains( rel2 ) ); assertThat( index.query( null, startNode, endNode1 ), contains( rel1 ) ); assertThat( index.get( "name", "something", null, endNode1 ), contains( rel1 ) ); assertThat( index.get( "name", "something", startNode, endNode2 ), contains( rel2 ) ); assertThat( index.get( null, null, startNode, endNode1 ), contains( rel1 ) ); restartTx(); } rel2.delete(); rel1.delete(); startNode.delete(); endNode1.delete(); endNode2.delete(); index.delete(); } @Test public void testSortByRelevance() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); Node node3 = graphDb.createNode(); index.add( node1, "name", "something" ); index.add( node2, "name", "something" ); index.add( node2, "foo", "yes" ); index.add( node3, "name", "something" ); index.add( node3, "foo", "yes" ); index.add( node3, "bar", "yes" ); restartTx(); IndexHits<Node> hits = index.query( new QueryContext( "+name:something foo:yes bar:yes" ).sort( Sort.RELEVANCE ) ); assertEquals( node3, hits.next() ); assertEquals( node2, hits.next() ); assertEquals( node1, hits.next() ); assertFalse( hits.hasNext() ); index.delete(); node1.delete(); node2.delete(); node3.delete(); } @Test public void testSorting() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String name = "name"; String title = "title"; String other = "other"; String sex = "sex"; Node adam = graphDb.createNode(); Node adam2 = graphDb.createNode(); Node jack = graphDb.createNode(); Node eva = graphDb.createNode(); index.add( adam, name, "Adam" ); index.add( adam, title, "Software developer" ); index.add( adam, sex, "male" ); index.add( adam, other, "aaa" ); index.add( adam2, name, "Adam" ); index.add( adam2, title, "Blabla" ); index.add( adam2, sex, "male" ); index.add( adam2, other, "bbb" ); index.add( jack, name, "Jack" ); index.add( jack, title, "Apple sales guy" ); index.add( jack, sex, "male" ); index.add( jack, other, "ccc" ); index.add( eva, name, "Eva" ); index.add( eva, title, "Secretary" ); index.add( eva, sex, "female" ); index.add( eva, other, "ddd" ); for ( int i = 0; i < 2; i++ ) { assertContainsInOrder( index.query( new QueryContext( "name:*" ).sort( name, title ) ), adam2, adam, eva, jack ); assertContainsInOrder( index.query( new QueryContext( "name:*" ).sort( name, other ) ), adam, adam2, eva, jack ); assertContainsInOrder( index.query( new QueryContext( "name:*" ).sort( sex, title ) ), eva, jack, adam2, adam ); assertContainsInOrder( index.query( name, new QueryContext( "*" ).sort( sex, title ) ), eva, jack, adam2, adam ); assertContainsInOrder( index.query( new QueryContext( "name:*" ).sort( name, title ).top( 2 ) ), adam2, adam ); restartTx(); } } @Test public void testNumericValuesExactIndex() throws Exception { testNumericValues( nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @Test public void testNumericValuesFulltextIndex() throws Exception { testNumericValues( nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } private void testNumericValues( Index<Node> index ) { Node node10 = graphDb.createNode(); Node node6 = graphDb.createNode(); Node node31 = graphDb.createNode(); String key = "key"; index.add( node10, key, numeric( 10 ) ); index.add( node6, key, numeric( 6 ) ); index.add( node31, key, numeric( 31 ) ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( NumericRangeQuery.newIntRange( key, 4, 40, true, true ) ), contains( node10, node6, node31 ) ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 6, 15, true, true ) ), contains( node10, node6 ) ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 6, 15, false, true ) ), contains( node10 ) ); restartTx(); } index.remove( node6, key, numeric( 6 ) ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 4, 40, true, true ) ), contains( node10, node31 ) ); restartTx(); assertThat( index.query( NumericRangeQuery.newIntRange( key, 4, 40, true, true ) ), contains( node10, node31 ) ); } @Test public void testNumericValueArrays() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); index.add( node1, "number", new ValueContext[]{ numeric( 45 ), numeric( 98 ) } ); Node node2 = graphDb.createNode(); index.add( node2, "number", new ValueContext[]{ numeric( 47 ), numeric( 100 ) } ); IndexHits<Node> indexResult1 = index.query( "number", newIntRange( "number", 47, 98, true, true ) ); assertThat( indexResult1, contains( node1, node2 ) ); assertThat( indexResult1.size(), is( 2 )); IndexHits<Node> indexResult2 = index.query( "number", newIntRange( "number", 44, 46, true, true ) ); assertThat( indexResult2, contains( node1 ) ); assertThat( indexResult2.size(), is( 1 ) ); IndexHits<Node> indexResult3 = index.query( "number", newIntRange( "number", 99, 101, true, true ) ); assertThat( indexResult3, contains( node2 ) ); assertThat( indexResult3.size(), is( 1 ) ); IndexHits<Node> indexResult4 = index.query( "number", newIntRange( "number", 47, 98, false, false ) ); assertThat( indexResult4, isEmpty() ); IndexHits<Node> indexResult5 = index.query( "number", numericRange( "number", null, 98, true, true ) ); assertContains( indexResult5, node1, node2 ); IndexHits<Node> indexResult6 = index.query( "number", numericRange( "number", 47, null, true, true ) ); assertContains( indexResult6, node1, node2 ); } @Test public void testRemoveNumericValues() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); String key = "key"; index.add( node1, key, new ValueContext( 15 ).indexNumeric() ); index.add( node2, key, new ValueContext( 5 ).indexNumeric() ); index.remove( node1, key, new ValueContext( 15 ).indexNumeric() ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), contains( node2 ) ); index.remove( node2, key, new ValueContext( 5 ).indexNumeric() ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), isEmpty() ); restartTx(); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), isEmpty() ); index.add( node1, key, new ValueContext( 15 ).indexNumeric() ); index.add( node2, key, new ValueContext( 5 ).indexNumeric() ); restartTx(); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), contains( node1, node2 ) ); index.remove( node1, key, new ValueContext( 15 ).indexNumeric() ); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), contains( node2 ) ); restartTx(); assertThat( index.query( NumericRangeQuery.newIntRange( key, 0, 20, false, false ) ), contains( node2 ) ); } @Test public void sortNumericValues() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); Node node3 = graphDb.createNode(); String key = "key"; index.add( node1, key, numeric( 5 ) ); index.add( node2, key, numeric( 15 ) ); index.add( node3, key, numeric( 10 ) ); restartTx(); assertContainsInOrder( index.query( numericRange( key, 5, 15 ).sortNumeric( key, false ) ), node1, node3, node2 ); } @Test public void testIndexNumberAsString() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); index.add( node1, "key", 10 ); for ( int i = 0; i < 2; i++ ) { assertEquals( node1, index.get( "key", 10 ).getSingle() ); assertEquals( node1, index.get( "key", "10" ).getSingle() ); assertEquals( node1, index.query( "key", 10 ).getSingle() ); assertEquals( node1, index.query( "key", "10" ).getSingle() ); restartTx(); } } @Test( expected = IllegalArgumentException.class ) public void makeSureIndexGetsCreatedImmediately() { // Since index creation is done outside of the normal transactions, // a rollback will not roll back index creation. nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); assertTrue( graphDb.index().existsForNodes( currentIndexName() ) ); rollbackTx(); assertTrue( graphDb.index().existsForNodes( currentIndexName() ) ); nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); } @Test public void makeSureFulltextConfigIsCaseInsensitiveByDefault() { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node node = graphDb.createNode(); String key = "name"; String value = "Mattias Persson"; index.add( node, key, value ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( "name", "[A TO Z]" ), contains( node ) ); assertThat( index.query( "name", "[a TO z]" ), contains( node ) ); assertThat( index.query( "name", "Mattias" ), contains( node ) ); assertThat( index.query( "name", "mattias" ), contains( node ) ); assertThat( index.query( "name", "Matt*" ), contains( node ) ); assertThat( index.query( "name", "matt*" ), contains( node ) ); restartTx(); } } @Test public void makeSureFulltextIndexCanBeCaseSensitive() { Index<Node> index = nodeIndex( MapUtil.stringMap( new HashMap<String, String>( LuceneIndexImplementation.FULLTEXT_CONFIG ), "to_lower_case", "false" ) ); Node node = graphDb.createNode(); String key = "name"; String value = "Mattias Persson"; index.add( node, key, value ); for ( int i = 0; i < 2; i++ ) { assertThat( index.query( "name", "[A TO Z]" ), contains( node ) ); assertThat( index.query( "name", "[a TO z]" ), isEmpty() ); assertThat( index.query( "name", "Matt*" ), contains( node ) ); assertThat( index.query( "name", "matt*" ), isEmpty() ); assertThat( index.query( "name", "Persson" ), contains( node ) ); assertThat( index.query( "name", "persson" ), isEmpty() ); restartTx(); } } @Test public void makeSureCustomAnalyzerCanBeUsed() { CustomAnalyzer.called = false; Index<Node> index = nodeIndex( MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "analyzer", org.neo4j.index.impl.lucene.CustomAnalyzer.class.getName(), "to_lower_case", "true" ) ); Node node = graphDb.createNode(); String key = "name"; String value = "The value"; index.add( node, key, value ); restartTx(); assertTrue( CustomAnalyzer.called ); assertThat( index.query( key, "[A TO Z]" ), contains( node ) ); } @Test public void makeSureCustomAnalyzerCanBeUsed2() { CustomAnalyzer.called = false; Index<Node> index = nodeIndex( "w-custom-analyzer-2", MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "analyzer", org.neo4j.index.impl.lucene.CustomAnalyzer.class.getName(), "to_lower_case", "true", "type", "fulltext" ) ); Node node = graphDb.createNode(); String key = "name"; String value = "The value"; index.add( node, key, value ); restartTx(); assertTrue( CustomAnalyzer.called ); assertThat( index.query( key, "[A TO Z]" ), contains( node ) ); } @Test public void makeSureIndexNameAndConfigCanBeReachedFromIndex() { String indexName = "my-index-1"; Index<Node> nodeIndex = nodeIndex( indexName, LuceneIndexImplementation.EXACT_CONFIG ); assertEquals( indexName, nodeIndex.getName() ); assertEquals( LuceneIndexImplementation.EXACT_CONFIG, graphDb.index().getConfiguration( nodeIndex ) ); String indexName2 = "my-index-2"; Index<Relationship> relIndex = relationshipIndex( indexName2, LuceneIndexImplementation.FULLTEXT_CONFIG ); assertEquals( indexName2, relIndex.getName() ); assertEquals( LuceneIndexImplementation.FULLTEXT_CONFIG, graphDb.index().getConfiguration( relIndex ) ); } @Test public void testStringQueryVsQueryObject() throws IOException { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "name", "Mattias Persson" ); for ( int i = 0; i < 2; i++ ) { assertContains( index.query( "name:Mattias AND name:Per*" ), node ); assertContains( index.query( "name:mattias" ), node ); assertContains( index.query( new TermQuery( new Term( "name", "mattias" ) ) ), node ); restartTx(); } assertNull( index.query( new TermQuery( new Term( "name", "Mattias" ) ) ).getSingle() ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void testAbandonedIds( EntityCreator<T> creator, Index<T> index ) { // TODO This doesn't actually test that they are deleted, it just triggers it // so that you manually can inspect what's going on T a = creator.create(); T b = creator.create(); T c = creator.create(); String key = "name"; String value = "value"; index.add( a, key, value ); index.add( b, key, value ); index.add( c, key, value ); restartTx(); creator.delete( b ); restartTx(); IteratorUtil.count( (Iterator<Node>) index.get( key, value ) ); rollbackTx(); beginTx(); IteratorUtil.count( (Iterator<Node>) index.get( key, value ) ); index.add( c, "something", "whatever" ); restartTx(); IteratorUtil.count( (Iterator<Node>) index.get( key, value ) ); } @Test public void testAbandonedNodeIds() { testAbandonedIds( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @Test public void testAbandonedNodeIdsFulltext() { testAbandonedIds( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @Test public void testAbandonedRelIds() { testAbandonedIds( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @Test public void testAbandonedRelIdsFulltext() { testAbandonedIds( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @Test public void makeSureYouCanRemoveFromRelationshipIndex() { Node n1 = graphDb.createNode(); Node n2 = graphDb.createNode(); Relationship r = n1.createRelationshipTo( n2, DynamicRelationshipType.withName( "foo" ) ); RelationshipIndex index = graphDb.index().forRelationships( "rel-index" ); String key = "bar"; index.remove( r, key, "value" ); index.add( r, key, "otherValue" ); for ( int i = 0; i < 2; i++ ) { assertThat( index.get( key, "value" ), isEmpty() ); assertThat( index.get( key, "otherValue" ), contains( r ) ); restartTx(); } } @Test public void makeSureYouCanGetEntityTypeFromIndex() { Index<Node> nodeIndex = nodeIndex( MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "exact" ) ); Index<Relationship> relIndex = relationshipIndex( MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "exact" ) ); assertEquals( Node.class, nodeIndex.getEntityType() ); assertEquals( Relationship.class, relIndex.getEntityType() ); } @Test public void makeSureConfigurationCanBeModified() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); try { graphDb.index().setConfiguration( index, IndexManager.PROVIDER, "something" ); fail( "Shouldn't be able to modify provider" ); } catch ( IllegalArgumentException e ) { /* Good*/ } try { graphDb.index().removeConfiguration( index, IndexManager.PROVIDER ); fail( "Shouldn't be able to modify provider" ); } catch ( IllegalArgumentException e ) { /* Good*/ } String key = "my-key"; String value = "my-value"; String newValue = "my-new-value"; assertNull( graphDb.index().setConfiguration( index, key, value ) ); assertEquals( value, graphDb.index().getConfiguration( index ).get( key ) ); assertEquals( value, graphDb.index().setConfiguration( index, key, newValue ) ); assertEquals( newValue, graphDb.index().getConfiguration( index ).get( key ) ); assertEquals( newValue, graphDb.index().removeConfiguration( index, key ) ); assertNull( graphDb.index().getConfiguration( index ).get( key ) ); } @Test public void makeSureSlightDifferencesInIndexConfigCanBeSupplied() { Map<String, String> config = MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ); String name = currentIndexName(); nodeIndex( name, config ); nodeIndex( name, MapUtil.stringMap( new HashMap<String, String>( config ), "to_lower_case", "true" ) ); try { nodeIndex( name, MapUtil.stringMap( new HashMap<String, String>( config ), "to_lower_case", "false" ) ); fail( "Shouldn't be able to get index with these kinds of differences in config" ); } catch ( IllegalArgumentException e ) { /* */ } nodeIndex( name, MapUtil.stringMap( new HashMap<String, String>( config ), "whatever", "something" ) ); } @Test public void testScoring() { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); String key = "text"; // Where the heck did I get this sentence from? index.add( node1, key, "a time where no one was really awake" ); index.add( node2, key, "once upon a time there was" ); restartTx(); IndexHits<Node> hits = index.query( key, new QueryContext( "once upon a time was" ).sort( Sort.RELEVANCE ) ); Node hit1 = hits.next(); float score1 = hits.currentScore(); Node hit2 = hits.next(); float score2 = hits.currentScore(); assertEquals( node2, hit1 ); assertEquals( node1, hit2 ); assertTrue( score1 > score2 ); } @Test public void testTopHits() { Index<Relationship> index = relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); EntityCreator<Relationship> creator = RELATIONSHIP_CREATOR; String key = "text"; Relationship rel1 = creator.create( key, "one two three four five six seven eight nine ten" ); Relationship rel2 = creator.create( key, "one two three four five six seven eight other things" ); Relationship rel3 = creator.create( key, "one two three four five six some thing else" ); Relationship rel4 = creator.create( key, "one two three four five what ever" ); Relationship rel5 = creator.create( key, "one two three four all that is good and bad" ); Relationship rel6 = creator.create( key, "one two three hill or something" ); Relationship rel7 = creator.create( key, "one two other time than this" ); index.add( rel2, key, rel2.getProperty( key ) ); index.add( rel1, key, rel1.getProperty( key ) ); index.add( rel3, key, rel3.getProperty( key ) ); index.add( rel7, key, rel7.getProperty( key ) ); index.add( rel5, key, rel5.getProperty( key ) ); index.add( rel4, key, rel4.getProperty( key ) ); index.add( rel6, key, rel6.getProperty( key ) ); String query = "one two three four five six seven"; for ( int i = 0; i < 2; i++ ) { assertContainsInOrder( index.query( key, new QueryContext( query ).top( 3 ).sort( Sort.RELEVANCE ) ), rel1, rel2, rel3 ); restartTx(); } } @Test public void testSimilarity() { Index<Node> index = nodeIndex( MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext", "similarity", DefaultSimilarity.class.getName() ) ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); restartTx(); assertContains( index.get( "key", "value" ), node ); } @Test public void testCombinedHitsSizeProblem() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); Node node3 = graphDb.createNode(); String key = "key"; String value = "value"; index.add( node1, key, value ); index.add( node2, key, value ); restartTx(); index.add( node3, key, value ); IndexHits<Node> hits = index.get( key, value ); assertEquals( 3, hits.size() ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void testRemoveWithoutKey( EntityCreator<T> creator, Index<T> index ) throws Exception { String key1 = "key1"; String key2 = "key2"; String value = "value"; T entity1 = creator.create(); index.add( entity1, key1, value ); index.add( entity1, key2, value ); T entity2 = creator.create(); index.add( entity2, key1, value ); index.add( entity2, key2, value ); restartTx(); assertContains( index.get( key1, value ), entity1, entity2 ); assertContains( index.get( key2, value ), entity1, entity2 ); index.remove( entity1, key2 ); assertContains( index.get( key1, value ), entity1, entity2 ); assertContains( index.get( key2, value ), entity2 ); index.add( entity1, key2, value ); for ( int i = 0; i < 2; i++ ) { assertContains( index.get( key1, value ), entity1, entity2 ); assertContains( index.get( key2, value ), entity1, entity2 ); restartTx(); } } @Test public void testRemoveWithoutKeyNodes() throws Exception { testRemoveWithoutKey( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @Test public void testRemoveWithoutKeyRelationships() throws Exception { testRemoveWithoutKey( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void testRemoveWithoutKeyValue( EntityCreator<T> creator, Index<T> index ) throws Exception { String key1 = "key1"; String value1 = "value1"; String key2 = "key2"; String value2 = "value2"; T entity1 = creator.create(); index.add( entity1, key1, value1 ); index.add( entity1, key2, value2 ); T entity2 = creator.create(); index.add( entity2, key1, value1 ); index.add( entity2, key2, value2 ); restartTx(); assertContains( index.get( key1, value1 ), entity1, entity2 ); assertContains( index.get( key2, value2 ), entity1, entity2 ); index.remove( entity1 ); assertContains( index.get( key1, value1 ), entity2 ); assertContains( index.get( key2, value2 ), entity2 ); index.add( entity1, key1, value1 ); for ( int i = 0; i < 2; i++ ) { assertContains( index.get( key1, value1 ), entity1, entity2 ); assertContains( index.get( key2, value2 ), entity2 ); restartTx(); } } @Test public void testRemoveWithoutKeyValueNodes() throws Exception { testRemoveWithoutKeyValue( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @Test public void testRemoveWithoutKeyValueRelationships() throws Exception { testRemoveWithoutKeyValue( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ) ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void testRemoveWithoutKeyFulltext( EntityCreator<T> creator, Index<T> index ) throws Exception { String key1 = "key1"; String key2 = "key2"; String value1 = "value one"; String value2 = "other value"; String value = "value"; T entity1 = creator.create(); index.add( entity1, key1, value1 ); index.add( entity1, key2, value1 ); index.add( entity1, key2, value2 ); T entity2 = creator.create(); index.add( entity2, key1, value1 ); index.add( entity2, key2, value1 ); index.add( entity2, key2, value2 ); restartTx(); assertContains( index.query( key1, value ), entity1, entity2 ); assertContains( index.query( key2, value ), entity1, entity2 ); index.remove( entity1, key2 ); assertContains( index.query( key1, value ), entity1, entity2 ); assertContains( index.query( key2, value ), entity2 ); index.add( entity1, key2, value1 ); for ( int i = 0; i < 2; i++ ) { assertContains( index.query( key1, value ), entity1, entity2 ); assertContains( index.query( key2, value ), entity1, entity2 ); restartTx(); } } @Test public void testRemoveWithoutKeyFulltextNode() throws Exception { testRemoveWithoutKeyFulltext( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @Test public void testRemoveWithoutKeyFulltextRelationship() throws Exception { testRemoveWithoutKeyFulltext( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @SuppressWarnings( "unchecked" ) private <T extends PropertyContainer> void testRemoveWithoutKeyValueFulltext( EntityCreator<T> creator, Index<T> index ) throws Exception { String value = "value"; String key1 = "key1"; String value1 = value + " one"; String key2 = "key2"; String value2 = value + " two"; T entity1 = creator.create(); index.add( entity1, key1, value1 ); index.add( entity1, key2, value2 ); T entity2 = creator.create(); index.add( entity2, key1, value1 ); index.add( entity2, key2, value2 ); restartTx(); assertContains( index.query( key1, value ), entity1, entity2 ); assertContains( index.query( key2, value ), entity1, entity2 ); index.remove( entity1 ); assertContains( index.query( key1, value ), entity2 ); assertContains( index.query( key2, value ), entity2 ); index.add( entity1, key1, value1 ); for ( int i = 0; i < 2; i++ ) { assertContains( index.query( key1, value ), entity1, entity2 ); assertContains( index.query( key2, value ), entity2 ); restartTx(); } } @Test public void testRemoveWithoutKeyValueFulltextNode() throws Exception { testRemoveWithoutKeyValueFulltext( NODE_CREATOR, nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @Test public void testRemoveWithoutKeyValueFulltextRelationship() throws Exception { testRemoveWithoutKeyValueFulltext( RELATIONSHIP_CREATOR, relationshipIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ) ); } @Test public void testSortingWithTopHitsInPartCommittedPartLocal() { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); Node first = graphDb.createNode(); Node second = graphDb.createNode(); Node third = graphDb.createNode(); Node fourth = graphDb.createNode(); String key = "key"; index.add( third, key, "ccc" ); index.add( second, key, "bbb" ); restartTx(); index.add( fourth, key, "ddd" ); index.add( first, key, "aaa" ); assertContainsInOrder( index.query( key, new QueryContext( "*" ).sort( key ) ), first, second, third, fourth ); assertContainsInOrder( index.query( key, new QueryContext( "*" ).sort( key ).top( 2 ) ), first, second ); } @Test public void shouldNotFindValueDeletedInSameTx() { Index<Node> nodeIndex = graphDb.index().forNodes( "size-after-removal" ); Node node = graphDb.createNode(); nodeIndex.add( node, "key", "value" ); restartTx(); nodeIndex.remove( node ); for ( int i = 0; i < 2; i++ ) { IndexHits<Node> hits = nodeIndex.get( "key", "value" ); assertEquals( 0, hits.size() ); assertNull( hits.getSingle() ); hits.close(); restartTx(); } } @Test public void notAbleToIndexWithForbiddenKey() throws Exception { Index<Node> index = graphDb.index().forNodes( "check-for-null" ); Node node = graphDb.createNode(); try { index.add( node, null, "not allowed" ); fail( "Shouldn't be able to index something with null key" ); } catch ( IllegalArgumentException e ) { // OK } try { index.add( node, "_id_", "not allowed" ); fail( "Shouldn't be able to index something with null key" ); } catch ( IllegalArgumentException e ) { // OK } } private Node createAndIndexNode( Index<Node> index, String key, String value ) { Node node = graphDb.createNode(); node.setProperty( key, value ); index.add( node, key, value ); return node; } @Test public void testRemoveNodeFromIndex() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "key"; String value = "MYID"; Node node = createAndIndexNode( index, key, value ); index.remove( node ); node.delete(); Node node2 = createAndIndexNode( index, key, value ); assertEquals( node2, index.get( key, value ).getSingle() ); } @Test public void canQueryWithWildcardEvenIfAlternativeRemovalMethodsUsedInSameTx1() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); restartTx(); index.remove( node, "key" ); assertNull( index.query( "key", "v*" ).getSingle() ); assertNull( index.query( "key", "*" ).getSingle() ); } @Test public void canQueryWithWildcardEvenIfAlternativeRemovalMethodsUsedInSameTx2() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "key", "value" ); restartTx(); index.remove( node ); assertNull( index.query( "key", "v*" ).getSingle() ); assertNull( index.query( "key", "*" ).getSingle() ); } @Ignore( "TODO Exposes a bug. Fixed in Lucene 3.4.0" ) @Test public void updateIndex() throws Exception { String TEXT = "text"; String NUMERIC = "numeric"; String TEXT_1 = "text_1"; Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node n = graphDb.createNode(); index.add(n, NUMERIC, new ValueContext(5).indexNumeric()); index.add(n, TEXT, "text"); index.add(n, TEXT_1, "text"); commitTx(); assertNotNull( index.query(QueryContext.numericRange(NUMERIC, 5, 5, true, true)).getSingle() ); assertNotNull( index.get(TEXT_1, "text").getSingle() ); beginTx(); // Following line may be commented, it's addition of node that causes the problem index.remove(n, TEXT, "text"); index.add(n, TEXT, "text 1"); commitTx(); assertNotNull( index.get(TEXT_1, "text").getSingle() ); // Test fails here assertNotNull( index.query(QueryContext.numericRange(NUMERIC, 5, 5, true, true)).getSingle() ); } @Test public void exactIndexWithCaseInsensitive() throws Exception { Index<Node> index = nodeIndex( stringMap( "analyzer", LowerCaseKeywordAnalyzer.class.getName() ) ); Node node = graphDb.createNode(); index.add( node, "name", "Thomas Anderson" ); assertContains( index.query( "name", "\"Thomas Anderson\"" ), node ); assertContains( index.query( "name", "\"thoMas ANDerson\"" ), node ); restartTx(); assertContains( index.query( "name", "\"Thomas Anderson\"" ), node ); assertContains( index.query( "name", "\"thoMas ANDerson\"" ), node ); } @Test public void exactIndexWithCaseInsensitiveWithBetterConfig() throws Exception { // START SNIPPET: exact-case-insensitive Index<Node> index = graphDb.index().forNodes( "exact-case-insensitive", stringMap( "type", "exact", "to_lower_case", "true" ) ); Node node = graphDb.createNode(); index.add( node, "name", "Thomas Anderson" ); assertContains( index.query( "name", "\"Thomas Anderson\"" ), node ); assertContains( index.query( "name", "\"thoMas ANDerson\"" ), node ); // END SNIPPET: exact-case-insensitive restartTx(); assertContains( index.query( "name", "\"Thomas Anderson\"" ), node ); assertContains( index.query( "name", "\"thoMas ANDerson\"" ), node ); } @Test public void notAbleToRemoveWithForbiddenKey() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "name", "Mattias" ); restartTx(); try { index.remove( node, null ); fail( "Shouldn't be able to" ); } catch ( IllegalArgumentException e ) { // OK } try { index.remove( node, "_id_" ); fail( "Shouldn't be able to" ); } catch ( IllegalArgumentException e ) { // OK } } @Ignore( "an issue that should be fixed at some point" ) @Test( expected = NotFoundException.class ) public void shouldNotBeAbleToIndexNodeThatIsNotCommitted() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); String key = "noob"; String value = "Johan"; WorkThread thread = new WorkThread( index, graphDb, node ); thread.beginTransaction(); try { thread.add( node, key, value ); } finally { thread.rollback(); } } @Test public void putIfAbsentSingleThreaded() { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); String key = "name"; String value = "Mattias"; String value2 = "Persson"; assertNull( index.putIfAbsent( node, key, value ) ); assertEquals( node, index.get( key, value ).getSingle() ); assertNotNull( index.putIfAbsent( node, key, value ) ); assertNull( index.putIfAbsent( node, key, value2 ) ); assertNotNull( index.putIfAbsent( node, key, value2 ) ); restartTx(); assertNotNull( index.putIfAbsent( node, key, value ) ); assertNotNull( index.putIfAbsent( node, key, value2 ) ); assertEquals( node, index.get( key, value ).getSingle() ); } @Test public void putIfAbsentMultiThreaded() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); commitTx(); String key = "name"; String value = "Mattias"; WorkThread t1 = new WorkThread( index, graphDb, node ); WorkThread t2 = new WorkThread( index, graphDb, node ); t1.beginTransaction(); t2.beginTransaction(); assertNull( t2.putIfAbsent( node, key, value ).get() ); Future<Node> futurePut = t1.putIfAbsent( node, key, value ); t1.waitUntilWaiting(); t2.commit(); assertNotNull( futurePut.get() ); t1.commit(); assertEquals( node, index.get( key, value ).getSingle() ); t1.shutdown(); t2.shutdown(); } @Test public void putIfAbsentOnOtherValueInOtherThread() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); commitTx(); String key = "name"; String value = "Mattias"; String otherValue = "Tobias"; WorkThread t1 = new WorkThread( index, graphDb, node ); WorkThread t2 = new WorkThread( index, graphDb, node ); t1.beginTransaction(); t2.beginTransaction(); assertNull( t2.putIfAbsent( node, key, value ).get() ); Future<Node> futurePut = t1.putIfAbsent( node, key, otherValue ); t2.commit(); assertNull( futurePut.get() ); t1.commit(); assertEquals( node, index.get( key, value ).getSingle() ); assertEquals( node, index.get( key, otherValue ).getSingle() ); t1.shutdown(); t2.shutdown(); } @Test public void putIfAbsentOnOtherKeyInOtherThread() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); commitTx(); String key = "name"; String otherKey = "friend"; String value = "Mattias"; WorkThread t1 = new WorkThread( index, graphDb, node ); WorkThread t2 = new WorkThread( index, graphDb, node ); t1.beginTransaction(); t2.beginTransaction(); assertNull( t2.putIfAbsent( node, key, value ).get() ); assertNull( t1.putIfAbsent( node, otherKey, value ).get() ); t2.commit(); t1.commit(); assertEquals( node, index.get( key, value ).getSingle() ); assertEquals( node, index.get( otherKey, value ).getSingle() ); t1.shutdown(); t2.shutdown(); } @Test public void putIfAbsentShouldntBlockIfNotAbsent() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); String key = "key"; String value = "value"; index.add( node, key, value ); restartTx(); WorkThread otherThread = new WorkThread( index, graphDb, node ); otherThread.beginTransaction(); // Should not grab lock index.putIfAbsent( node, key, value ); // Should be able to complete right away assertNotNull( otherThread.putIfAbsent( node, key, value ).get() ); otherThread.commit(); commitTx(); otherThread.shutdown(); } @Test public void getOrCreateNodeWithUniqueFactory() throws Exception { final String key = "name"; final String value = "Mattias"; final String property = "counter"; final Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); final AtomicInteger counter = new AtomicInteger(); UniqueFactory<Node> factory = new UniqueFactory.UniqueNodeFactory( index ) { @Override protected void initialize( Node node, Map<String, Object> properties ) { assertEquals( value, properties.get( key ) ); assertEquals( 1, properties.size() ); node.setProperty( property, counter.getAndIncrement() ); } }; Node unique = factory.getOrCreate( key, value ); assertNotNull( unique ); assertEquals( "not initialized", 0, unique.getProperty( property, null ) ); assertEquals( unique, index.get( key, value ).getSingle() ); assertEquals( unique, factory.getOrCreate( key, value ) ); assertEquals( "initialized more than once", 0, unique.getProperty( property ) ); assertEquals( unique, index.get( key, value ).getSingle() ); } @Test public void getOrCreateRelationshipWithUniqueFactory() throws Exception { final String key = "name"; final String value = "Mattias"; final Node root = graphDb.createNode(); final Index<Relationship> index = relationshipIndex( LuceneIndexImplementation.EXACT_CONFIG ); final DynamicRelationshipType type = DynamicRelationshipType.withName( "SINGLE" ); UniqueFactory<Relationship> factory = new UniqueFactory.UniqueRelationshipFactory( index ) { @Override protected Relationship create( Map<String, Object> properties ) { assertEquals( value, properties.get( key ) ); assertEquals( 1, properties.size() ); return root.createRelationshipTo( graphDatabase().createNode(), type ); } }; Relationship unique = factory.getOrCreate( key, value ); assertEquals( unique, root.getSingleRelationship( type, Direction.BOTH ) ); assertNotNull( unique ); assertEquals( unique, index.get( key, value ).getSingle() ); assertEquals( unique, factory.getOrCreate( key, value ) ); assertEquals( unique, root.getSingleRelationship( type, Direction.BOTH ) ); assertEquals( unique, index.get( key, value ).getSingle() ); } @Test public void getOrCreateMultiThreaded() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); String key = "name"; String value = "Mattias"; WorkThread t1 = new WorkThread( index, graphDb, null ); WorkThread t2 = new WorkThread( index, graphDb, null ); t1.beginTransaction(); t2.beginTransaction(); Node node = t2.getOrCreate( key, value, 0 ).get(); assertNotNull( node ); assertEquals( 0, t2.getProperty( node, key ) ); Future<Node> futurePut = t1.getOrCreate( key, value, 1 ); t1.waitUntilWaiting(); t2.commit(); assertEquals( node, futurePut.get() ); assertEquals( 0, t1.getProperty( node, key ) ); t1.commit(); assertEquals( node, index.get( key, value ).getSingle() ); t1.shutdown(); t2.shutdown(); } @Test public void useStandardAnalyzer() throws Exception { Index<Node> index = nodeIndex( stringMap( "analyzer", MyStandardAnalyzer.class.getName() ) ); Node node = graphDb.createNode(); index.add( node, "name", "Mattias" ); } @Test public void numericValueForGetInExactIndex() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); numericValueForGet( index ); } @Test public void numericValueForGetInFulltextIndex() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.FULLTEXT_CONFIG ); numericValueForGet( index ); } private void numericValueForGet( Index<Node> index ) { Node node = graphDb.createNode(); long id = 100L; index.add( node, "name", ValueContext.numeric( id ) ); assertEquals( node, index.get( "name", ValueContext.numeric( id ) ).getSingle() ); restartTx(); assertEquals( node, index.get( "name", ValueContext.numeric( id ) ).getSingle() ); } @Test public void combinedNumericalQuery() throws Exception { Index<Node> index = nodeIndex( LuceneIndexImplementation.EXACT_CONFIG ); Node node = graphDb.createNode(); index.add( node, "start", ValueContext.numeric( 10 ) ); index.add( node, "end", ValueContext.numeric( 20 ) ); restartTx(); BooleanQuery q = new BooleanQuery(); q.add( LuceneUtil.rangeQuery( "start", 9, null, true, true ), Occur.MUST ); q.add( LuceneUtil.rangeQuery( "end", null, 30, true, true ), Occur.MUST ); assertContains( index.query( q ), node ); } }