/**
* Copyright (c) 2002-2011 "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 Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero 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.QueryParser.Operator;
import org.apache.lucene.search.DefaultSimilarity;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.junit.Test;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
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.RelationshipIndex;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.index.Neo4jTestCase;
import org.neo4j.index.lucene.QueryContext;
import org.neo4j.index.lucene.ValueContext;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.*;
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.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( "failing-index", LuceneIndexImplementation.FULLTEXT_CONFIG );
Node node = graphDb.createNode();
index.add( node, "key", "value" );
assertThat( index.query( "key:value" ), contains( node ) );
}
@Test
public void testStartupInExistingDirectory() {
File dir = new File("target" + File.separator + "temp" + File.separator);
Neo4jTestCase.deleteFileOrDirectory( dir );
dir.mkdir();
EmbeddedGraphDatabase graphDatabase = new EmbeddedGraphDatabase( dir.getAbsolutePath() );
Index<Node> index = graphDatabase.index().forNodes("nodes");
assertNotNull(index);
}
@Test
public void makeSureAdditionsCanBeReadNodeExact()
{
makeSureAdditionsCanBeRead( nodeIndex( "exact", LuceneIndexImplementation.EXACT_CONFIG ),
NODE_CREATOR );
}
@Test
public void makeSureAdditionsCanBeReadNodeFulltext()
{
makeSureAdditionsCanBeRead( nodeIndex( "fulltext",
LuceneIndexImplementation.FULLTEXT_CONFIG ), NODE_CREATOR );
}
@Test
public void makeSureAdditionsCanBeReadRelationshipExact()
{
makeSureAdditionsCanBeRead( relationshipIndex( "exact",
LuceneIndexImplementation.EXACT_CONFIG ), RELATIONSHIP_CREATOR );
}
@Test
public void makeSureAdditionsCanBeReadRelationshipFulltext()
{
makeSureAdditionsCanBeRead( relationshipIndex( "fulltext",
LuceneIndexImplementation.FULLTEXT_CONFIG ), RELATIONSHIP_CREATOR );
}
@Test
public void makeSureAdditionsCanBeRemovedInSameTx()
{
makeSureAdditionsCanBeRemoved( false );
}
@Test
public void makeSureYouCanAskIfAnIndexExistsOrNot()
{
String name = "index-that-may-exist";
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( "index", 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( "index", 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( "index", 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( "indexFooBar", 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( "index", 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( "index", 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( "index", 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( 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( 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( "index",
LuceneIndexImplementation.EXACT_CONFIG ), NODE_CREATOR );
}
@Test
public void doSomeRandomUseCaseTestingWithExactRelationshipIndex()
{
doSomeRandomUseCaseTestingWithExactIndex( relationshipIndex( "index",
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( "fulltext",
LuceneIndexImplementation.FULLTEXT_CONFIG ), NODE_CREATOR );
}
@Test
public void doSomeRandomTestingWithRelationshipFulltextInde()
{
doSomeRandomTestingWithFulltextIndex( relationshipIndex( "fulltext",
LuceneIndexImplementation.FULLTEXT_CONFIG ), RELATIONSHIP_CREATOR );
}
@Test
public void testNodeLocalRelationshipIndex()
{
RelationshipIndex index = relationshipIndex( "locality",
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( "relevance", 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( "sort", 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 testNumericValues()
{
Index<Node> index = nodeIndex( "numeric", LuceneIndexImplementation.EXACT_CONFIG );
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();
}
}
@Test
public void testRemoveNumericValues()
{
Index<Node> index = nodeIndex( "numeric2", 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 testIndexNumberAsString()
{
Index<Node> index = nodeIndex( "nums", 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( "immediate-index", LuceneIndexImplementation.FULLTEXT_CONFIG );
assertTrue( graphDb.index().existsForNodes( "immediate-index" ) );
rollbackTx();
assertTrue( graphDb.index().existsForNodes( "immediate-index" ) );
nodeIndex( "immediate-index", LuceneIndexImplementation.EXACT_CONFIG );
}
@Test
public void makeSureFulltextConfigIsCaseInsensitiveByDefault()
{
Index<Node> index = nodeIndex( "ft-case-sensitive", 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( "ft-case-insensitive", 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( "w-custom-analyzer", MapUtil.stringMap(
"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(
"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( "query-diff", 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( "abandoned", LuceneIndexImplementation.EXACT_CONFIG ) );
}
@Test
public void testAbandonedNodeIdsFulltext()
{
testAbandonedIds( NODE_CREATOR, nodeIndex( "abandonedf", LuceneIndexImplementation.FULLTEXT_CONFIG ) );
}
@Test
public void testAbandonedRelIds()
{
testAbandonedIds( RELATIONSHIP_CREATOR, relationshipIndex( "abandoned", LuceneIndexImplementation.EXACT_CONFIG ) );
}
@Test
public void testAbandonedRelIdsFulltext()
{
testAbandonedIds( RELATIONSHIP_CREATOR, relationshipIndex( "abandonedf", 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( "type-test", MapUtil.stringMap( "provider", "lucene", "type", "exact" ) );
Index<Relationship> relIndex = relationshipIndex( "type-test", MapUtil.stringMap( "provider", "lucene", "type", "exact" ) );
assertEquals( Node.class, nodeIndex.getEntityType() );
assertEquals( Relationship.class, relIndex.getEntityType() );
}
@Test
public void makeSureConfigurationCanBeModified()
{
Index<Node> index = nodeIndex( "conf-index", LuceneIndexImplementation.EXACT_CONFIG );
try
{
graphDb.index().setConfiguration( index, "provider", "something" );
fail( "Shouldn't be able to modify provider" );
}
catch ( IllegalArgumentException e ) { /* Good*/ }
try
{
graphDb.index().removeConfiguration( index, "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( "provider", "lucene", "type", "fulltext" );
String name = "the-name";
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( "score-index", 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( "topdocs", 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( "similarity", MapUtil.stringMap( "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( "size-npe", 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( "remove-wo-k",
LuceneIndexImplementation.EXACT_CONFIG ) );
}
@Test
public void testRemoveWithoutKeyRelationships() throws Exception
{
testRemoveWithoutKey( RELATIONSHIP_CREATOR, relationshipIndex( "remove-wo-k",
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( "remove-wo-kv",
LuceneIndexImplementation.EXACT_CONFIG ) );
}
@Test
public void testRemoveWithoutKeyValueRelationships() throws Exception
{
testRemoveWithoutKeyValue( RELATIONSHIP_CREATOR, relationshipIndex( "remove-wo-kv",
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( "remove-wo-k-f", LuceneIndexImplementation.FULLTEXT_CONFIG ) );
}
@Test
public void testRemoveWithoutKeyFulltextRelationship() throws Exception
{
testRemoveWithoutKeyFulltext( RELATIONSHIP_CREATOR,
relationshipIndex( "remove-wo-k-f", 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( "remove-wo-kv-f", LuceneIndexImplementation.FULLTEXT_CONFIG ) );
}
@Test
public void testRemoveWithoutKeyValueFulltextRelationship() throws Exception
{
testRemoveWithoutKeyValueFulltext( RELATIONSHIP_CREATOR,
relationshipIndex( "remove-wo-kv-f", LuceneIndexImplementation.FULLTEXT_CONFIG ) );
}
@Test
public void testSortingWithTopHitsInPartCommittedPartLocal()
{
Index<Node> index = nodeIndex( "mix", 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();
}
}
}