/**
* 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 java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.graphdb.index.AutoIndexer;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.ReadableIndex;
import org.neo4j.graphdb.index.RelationshipIndex;
import org.neo4j.test.ImpermanentGraphDatabase;
import static org.junit.Assert.*;
public class TestAutoIndexing
{
private ImpermanentGraphDatabase graphDb;
private Transaction tx;
private Map<String, String> config;
private void newTransaction()
{
if ( tx != null )
{
tx.success();
tx.finish();
}
tx = graphDb.beginTx();
}
private Map<String, String> getConfig()
{
if ( config == null )
{
config = new HashMap<String, String>();
}
return config;
}
@Before
public void startDb()
{
graphDb = new ImpermanentGraphDatabase( getConfig() );
}
@After
public void stopDb()
{
if ( tx != null )
{
tx.finish();
}
if ( graphDb != null )
{
graphDb.shutdown();
}
tx = null;
config = null;
graphDb = null;
}
@Test
public void testNodeAutoIndexFromAPISanity()
{
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
autoIndexer.startAutoIndexingProperty( "test_uuid" );
autoIndexer.setEnabled( true );
assertEquals( 1, autoIndexer.getAutoIndexedProperties().size() );
assertTrue( autoIndexer.getAutoIndexedProperties().contains(
"test_uuid" ) );
newTransaction();
Node node1 = graphDb.createNode();
node1.setProperty( "test_uuid", "node1" );
Node node2 = graphDb.createNode();
node2.setProperty( "test_uuid", "node2" );
newTransaction();
assertEquals(
node1,
autoIndexer.getAutoIndex().get( "test_uuid", "node1" ).getSingle() );
assertEquals(
node2,
autoIndexer.getAutoIndex().get( "test_uuid", "node2" ).getSingle() );
}
@Test
public void testAutoIndexesReportReadOnly()
{
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
assertFalse( autoIndexer.getAutoIndex().isWriteable() );
autoIndexer.startAutoIndexingProperty( "test_uuid" );
autoIndexer.setEnabled( true );
assertFalse( autoIndexer.getAutoIndex().isWriteable() );
}
@Test
public void testChangesAreVisibleInTransaction()
{
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
autoIndexer.startAutoIndexingProperty( "nodeProp" );
autoIndexer.setEnabled( true );
newTransaction();
Node node1 = graphDb.createNode();
node1.setProperty( "nodeProp", "nodePropValue" );
node1.setProperty( "nodePropNonIndexable", "valueWhatever" );
ReadableIndex<Node> nodeIndex = autoIndexer.getAutoIndex();
assertEquals( node1,
nodeIndex.get( "nodeProp", "nodePropValue" ).getSingle() );
newTransaction();
Node node2 = graphDb.createNode();
node2.setProperty( "nodeProp", "nodePropValue2" );
assertEquals( node2,
nodeIndex.get( "nodeProp", "nodePropValue2" ).getSingle() );
node2.setProperty( "nodeProp", "nodePropValue3" );
assertEquals( node2,
nodeIndex.get( "nodeProp", "nodePropValue3" ).getSingle() );
node2.removeProperty( "nodeProp" );
assertFalse( nodeIndex.get( "nodeProp", "nodePropValue2" ).hasNext() );
assertFalse( nodeIndex.get( "nodeProp", "nodePropValue3" ).hasNext() );
newTransaction();
assertEquals( node1,
nodeIndex.get( "nodeProp", "nodePropValue" ).getSingle() );
assertFalse( nodeIndex.get( "nodeProp", "nodePropValue2" ).hasNext() );
assertFalse( nodeIndex.get( "nodeProp", "nodePropValue3" ).hasNext() );
}
@Test
public void testRelationshipAutoIndexFromAPISanity()
{
final String propNameToIndex = "test";
AutoIndexer<Relationship> autoIndexer = graphDb.index().getRelationshipAutoIndexer();
autoIndexer.startAutoIndexingProperty( propNameToIndex );
autoIndexer.setEnabled( true );
newTransaction();
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
Node node3 = graphDb.createNode();
Relationship rel12 = node1.createRelationshipTo( node2,
DynamicRelationshipType.withName( "DYNAMIC" ) );
Relationship rel23 = node2.createRelationshipTo( node3,
DynamicRelationshipType.withName( "DYNAMIC" ) );
rel12.setProperty( propNameToIndex, "rel12" );
rel23.setProperty( propNameToIndex, "rel23" );
newTransaction();
assertEquals(
rel12,
autoIndexer.getAutoIndex().get( propNameToIndex,
"rel12" ).getSingle() );
assertEquals(
rel23,
autoIndexer.getAutoIndex().get( propNameToIndex,
"rel23" ).getSingle() );
}
@Test
public void testConfigAndAPICompatibility()
{
stopDb();
config = new HashMap<String, String>();
config.put( GraphDatabaseSettings.node_keys_indexable.name(), "nodeProp1, nodeProp2" );
config.put( GraphDatabaseSettings.relationship_keys_indexable.name(), "relProp1, relProp2" );
config.put( GraphDatabaseSettings.node_auto_indexing.name(), "true" );
config.put( GraphDatabaseSettings.relationship_auto_indexing.name(), "true" );
startDb();
assertTrue( graphDb.index().getNodeAutoIndexer().isEnabled() );
assertTrue( graphDb.index().getRelationshipAutoIndexer().isEnabled() );
AutoIndexer<Node> autoNodeIndexer = graphDb.index().getNodeAutoIndexer();
// Start auto indexing a new and an already auto indexed
autoNodeIndexer.startAutoIndexingProperty( "nodeProp1" );
autoNodeIndexer.startAutoIndexingProperty( "nodeProp3" );
assertEquals( 3, autoNodeIndexer.getAutoIndexedProperties().size() );
assertTrue( autoNodeIndexer.getAutoIndexedProperties().contains(
"nodeProp1" ) );
assertTrue( autoNodeIndexer.getAutoIndexedProperties().contains(
"nodeProp2" ) );
assertTrue( autoNodeIndexer.getAutoIndexedProperties().contains(
"nodeProp3" ) );
}
@Test
public void testSmallGraphWithNonIndexableProps() throws Exception
{
stopDb();
config = new HashMap<String, String>();
config.put( GraphDatabaseSettings.node_keys_indexable.name(), "nodeProp1, nodeProp2" );
config.put( GraphDatabaseSettings.relationship_keys_indexable.name(), "relProp1, relProp2" );
config.put( GraphDatabaseSettings.node_auto_indexing.name(), "true" );
config.put( GraphDatabaseSettings.relationship_auto_indexing.name(), "true" );
startDb();
assertTrue( graphDb.index().getNodeAutoIndexer().isEnabled() );
assertTrue( graphDb.index().getRelationshipAutoIndexer().isEnabled() );
newTransaction();
// Build the graph, a 3-cycle
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
Node node3 = graphDb.createNode();
Relationship rel12 = node1.createRelationshipTo( node2,
DynamicRelationshipType.withName( "DYNAMIC" ) );
Relationship rel23 = node2.createRelationshipTo( node3,
DynamicRelationshipType.withName( "DYNAMIC" ) );
Relationship rel31 = node3.createRelationshipTo( node1,
DynamicRelationshipType.withName( "DYNAMIC" ) );
// Nodes
node1.setProperty( "nodeProp1", "node1Value1" );
node1.setProperty( "nodePropNonIndexable1", "node1ValueNonIndexable" );
node2.setProperty( "nodeProp2", "node2Value1" );
node2.setProperty( "nodePropNonIndexable2", "node2ValueNonIndexable" );
node3.setProperty( "nodeProp1", "node3Value1" );
node3.setProperty( "nodeProp2", "node3Value2" );
node3.setProperty( "nodePropNonIndexable3", "node3ValueNonIndexable" );
// Relationships
rel12.setProperty( "relProp1", "rel12Value1" );
rel12.setProperty( "relPropNonIndexable1", "rel12ValueNonIndexable" );
rel23.setProperty( "relProp2", "rel23Value1" );
rel23.setProperty( "relPropNonIndexable2", "rel23ValueNonIndexable" );
rel31.setProperty( "relProp1", "rel31Value1" );
rel31.setProperty( "relProp2", "rel31Value2" );
rel31.setProperty( "relPropNonIndexable3", "rel31ValueNonIndexable" );
newTransaction();
// Committed, time to check
AutoIndexer<Node> autoNodeIndexer = graphDb.index().getNodeAutoIndexer();
assertEquals(
node1,
autoNodeIndexer.getAutoIndex().get( "nodeProp1", "node1Value1" ).getSingle() );
assertEquals(
node2,
autoNodeIndexer.getAutoIndex().get( "nodeProp2", "node2Value1" ).getSingle() );
assertEquals(
node3,
autoNodeIndexer.getAutoIndex().get( "nodeProp1", "node3Value1" ).getSingle() );
assertEquals(
node3,
autoNodeIndexer.getAutoIndex().get( "nodeProp2", "node3Value2" ).getSingle() );
assertFalse( autoNodeIndexer.getAutoIndex().get(
"nodePropNonIndexable1",
"node1ValueNonIndexable" ).hasNext() );
assertFalse( autoNodeIndexer.getAutoIndex().get(
"nodePropNonIndexable2",
"node2ValueNonIndexable" ).hasNext() );
assertFalse( autoNodeIndexer.getAutoIndex().get(
"nodePropNonIndexable3",
"node3ValueNonIndexable" ).hasNext() );
AutoIndexer<Relationship> autoRelIndexer = graphDb.index().getRelationshipAutoIndexer();
assertEquals(
rel12,
autoRelIndexer.getAutoIndex().get( "relProp1",
"rel12Value1" ).getSingle() );
assertEquals(
rel23,
autoRelIndexer.getAutoIndex().get( "relProp2",
"rel23Value1" ).getSingle() );
assertEquals(
rel31,
autoRelIndexer.getAutoIndex().get( "relProp1",
"rel31Value1" ).getSingle() );
assertEquals(
rel31,
autoRelIndexer.getAutoIndex().get( "relProp2",
"rel31Value2" ).getSingle() );
assertFalse( autoRelIndexer.getAutoIndex().get(
"relPropNonIndexable1",
"rel12ValueNonIndexable" ).hasNext() );
assertFalse( autoRelIndexer.getAutoIndex().get(
"relPropNonIndexable2",
"rel23ValueNonIndexable" ).hasNext() );
assertFalse( autoRelIndexer.getAutoIndex().get(
"relPropNonIndexable3",
"rel31ValueNonIndexable" ).hasNext() );
}
@Test
public void testDefaultIsOff()
{
newTransaction();
Node node1 = graphDb.createNode();
node1.setProperty( "testProp", "node1" );
newTransaction();
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
assertFalse( autoIndexer.getAutoIndex().get( "testProp", "node1" ).hasNext() );
}
@Test
public void testDefaulIfOffIsForEverything()
{
graphDb.index().getNodeAutoIndexer().setEnabled( true );
newTransaction();
Node node1 = graphDb.createNode();
node1.setProperty( "testProp", "node1" );
node1.setProperty( "testProp1", "node1" );
Node node2 = graphDb.createNode();
node2.setProperty( "testProp", "node2" );
node2.setProperty( "testProp1", "node2" );
newTransaction();
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
assertFalse( autoIndexer.getAutoIndex().get( "testProp", "node1" ).hasNext() );
assertFalse( autoIndexer.getAutoIndex().get( "testProp1", "node1" ).hasNext() );
assertFalse( autoIndexer.getAutoIndex().get( "testProp", "node2" ).hasNext() );
assertFalse( autoIndexer.getAutoIndex().get( "testProp1", "node2" ).hasNext() );
}
@Test
public void testDefaultIsOffIfExplicit() throws Exception
{
stopDb();
config = new HashMap<String, String>();
config.put( GraphDatabaseSettings.node_keys_indexable.name(), "nodeProp1, nodeProp2" );
config.put( GraphDatabaseSettings.relationship_keys_indexable.name(), "relProp1, relProp2" );
config.put( GraphDatabaseSettings.node_auto_indexing.name(), "false" );
config.put( GraphDatabaseSettings.relationship_auto_indexing.name(), "false" );
startDb();
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
autoIndexer.startAutoIndexingProperty( "testProp" );
newTransaction();
Node node1 = graphDb.createNode();
node1.setProperty( "nodeProp1", "node1" );
node1.setProperty( "nodeProp2", "node1" );
node1.setProperty( "testProp", "node1" );
newTransaction();
assertFalse( autoIndexer.getAutoIndex().get( "nodeProp1", "node1" ).hasNext() );
assertFalse( autoIndexer.getAutoIndex().get( "nodeProp2", "node1" ).hasNext() );
assertFalse( autoIndexer.getAutoIndex().get( "testProp", "node1" ).hasNext() );
}
@Test
public void testDefaultsAreSeparateForNodesAndRelationships()
throws Exception
{
stopDb();
config = new HashMap<String, String>();
config.put( GraphDatabaseSettings.node_keys_indexable.name(), "propName" );
config.put( GraphDatabaseSettings.node_auto_indexing.name(), "true" );
// Now only node properties named propName should be indexed.
startDb();
newTransaction();
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
node1.setProperty( "propName", "node1" );
node2.setProperty( "propName", "node2" );
node2.setProperty( "propName_", "node2" );
Relationship rel = node1.createRelationshipTo( node2,
DynamicRelationshipType.withName( "DYNAMIC" ) );
rel.setProperty( "propName", "rel1" );
newTransaction();
ReadableIndex<Node> autoIndex = graphDb.index().getNodeAutoIndexer().getAutoIndex();
assertEquals( node1, autoIndex.get( "propName", "node1" ).getSingle() );
assertEquals( node2, autoIndex.get( "propName", "node2" ).getSingle() );
assertFalse( graphDb.index().getRelationshipAutoIndexer().getAutoIndex().get(
"propName", "rel1" ).hasNext() );
}
@Test
public void testStartStopAutoIndexing() throws Exception
{
stopDb();
config = new HashMap<String, String>();
config.put( GraphDatabaseSettings.node_keys_indexable.name(), "propName" );
config.put( GraphDatabaseSettings.node_auto_indexing.name(), "true" );
// Now only node properties named propName should be indexed.
startDb();
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
assertTrue( autoIndexer.isEnabled() );
autoIndexer.setEnabled( false );
assertFalse( autoIndexer.isEnabled() );
newTransaction();
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
node1.setProperty( "propName", "node" );
newTransaction();
assertFalse( autoIndexer.getAutoIndex().get( "nodeProp1", "node1" ).hasNext() );
autoIndexer.setEnabled( true );
node2.setProperty( "propName", "node" );
newTransaction();
assertEquals( node2,
autoIndexer.getAutoIndex().get( "propName", "node" ).getSingle() );
}
@Test
public void testStopMonitoringProperty()
{
AutoIndexer<Node> autoIndexer = graphDb.index().getNodeAutoIndexer();
autoIndexer.setEnabled( true );
autoIndexer.startAutoIndexingProperty( "propName" );
newTransaction();
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
node1.setProperty( "propName", "node" );
newTransaction();
assertEquals(
node1,
autoIndexer.getAutoIndex().get( "propName", "node" ).getSingle() );
newTransaction();
// Setting just another property to autoindex
autoIndexer.startAutoIndexingProperty( "propName2" );
autoIndexer.stopAutoIndexingProperty( "propName" );
node2.setProperty( "propName", "propValue" );
Node node3 = graphDb.createNode();
node3.setProperty( "propName2", "propValue" );
newTransaction();
// Now node2 must be not there, node3 must be there and node1 should not have been touched
assertEquals(
node1,
autoIndexer.getAutoIndex().get( "propName", "node" ).getSingle() );
assertEquals(
node3,
autoIndexer.getAutoIndex().get( "propName2", "propValue" ).getSingle() );
// Now, since only propName2 is autoindexed, every other should be
// removed when touched, such as node1's propName
node1.setProperty( "propName", "newValue" );
newTransaction();
assertFalse( autoIndexer.getAutoIndex().get( "propName", "newValue" ).hasNext() );
}
@Test
public void testMutations() throws Exception
{
graphDb.index().getNodeAutoIndexer().startAutoIndexingProperty(
"nodeProp1" );
graphDb.index().getNodeAutoIndexer().startAutoIndexingProperty(
"nodeProp2" );
graphDb.index().getNodeAutoIndexer().setEnabled( true );
Transaction tx = graphDb.beginTx();
Node node1 = null, node2 = null, node3 = null, node4 = null;
try
{
// Create the primitives
node1 = graphDb.createNode();
node2 = graphDb.createNode();
node3 = graphDb.createNode();
node4 = graphDb.createNode();
// Add indexable and non-indexable properties
node1.setProperty( "nodeProp1", "nodeProp1Value" );
node2.setProperty( "nodeProp2", "nodeProp2Value" );
node3.setProperty( "nodeProp1", "nodeProp3Value" );
node4.setProperty( "nodeProp2", "nodeProp4Value" );
// Make things persistent
tx.success();
}
catch ( Exception e )
{
tx.failure();
}
finally
{
tx.finish();
}
/*
* Here both nodes are indexed. To demonstrate removal, we stop
* autoindexing nodeProp1.
*/
AutoIndexer<Node> nodeAutoIndexer = graphDb.index().getNodeAutoIndexer();
nodeAutoIndexer.stopAutoIndexingProperty( "nodeProp1" );
tx = graphDb.beginTx();
try
{
/*
* nodeProp1 is no longer auto indexed. It will be
* removed regardless. Note that node3 will remain.
*/
node1.setProperty( "nodeProp1", "nodeProp1Value2" );
/*
* node2 will be auto updated
*/
node2.setProperty( "nodeProp2", "nodeProp2Value2" );
/*
* remove node4 property nodeProp2 from index.
*/
node4.removeProperty( "nodeProp2" );
// Make things persistent
tx.success();
}
catch ( Exception e )
{
tx.failure();
}
finally
{
tx.finish();
}
// Verify
ReadableIndex<Node> nodeAutoIndex = nodeAutoIndexer.getAutoIndex();
// node1 is completely gone
assertFalse( nodeAutoIndex.get( "nodeProp1", "nodeProp1Value" ).hasNext() );
assertFalse( nodeAutoIndex.get( "nodeProp1", "nodeProp1Value2" ).hasNext() );
// node2 is updated
assertFalse( nodeAutoIndex.get( "nodeProp2", "nodeProp2Value" ).hasNext() );
assertEquals( node2,
nodeAutoIndex.get( "nodeProp2", "nodeProp2Value2" ).getSingle() );
/*
* node3 is still there, despite its nodeProp1 property not being monitored
* any more because it was not touched, contrary to node1.
*/
assertEquals( node3,
nodeAutoIndex.get( "nodeProp1", "nodeProp3Value" ).getSingle() );
// Finally, node4 is removed because the property was removed.
assertFalse( nodeAutoIndex.get( "nodeProp2", "nodeProp4Value" ).hasNext() );
// END SNIPPET: Mutations
}
@Test
public void testGettingAutoIndexByNameReturnsSomethingReadOnly()
{
// Create the node and relationship autoindexes
graphDb.index().getNodeAutoIndexer().setEnabled( true );
graphDb.index().getNodeAutoIndexer().startAutoIndexingProperty(
"nodeProp" );
graphDb.index().getRelationshipAutoIndexer().setEnabled( true );
graphDb.index().getRelationshipAutoIndexer().startAutoIndexingProperty(
"relProp" );
newTransaction();
Node node1 = graphDb.createNode();
Node node2 = graphDb.createNode();
Relationship rel = node1.createRelationshipTo( node2,
DynamicRelationshipType.withName( "FOO" ) );
node1.setProperty( "nodeProp", "nodePropValue" );
rel.setProperty( "relProp", "relPropValue" );
newTransaction();
assertEquals( 1, graphDb.index().nodeIndexNames().length );
assertEquals( 1, graphDb.index().relationshipIndexNames().length );
assertEquals( "node_auto_index", graphDb.index().nodeIndexNames()[0] );
assertEquals( "relationship_auto_index",
graphDb.index().relationshipIndexNames()[0] );
Index<Node> nodeIndex = graphDb.index().forNodes( "node_auto_index" );
RelationshipIndex relIndex = graphDb.index().forRelationships(
"relationship_auto_index" );
assertEquals( node1,
nodeIndex.get( "nodeProp", "nodePropValue" ).getSingle() );
assertEquals( rel,
relIndex.get( "relProp", "relPropValue" ).getSingle() );
try
{
nodeIndex.add( null, null, null );
fail("Auto indexes should not allow external manipulation");
}
catch ( UnsupportedOperationException e )
{ // good
}
try
{
relIndex.add( null, null, null );
fail( "Auto indexes should not allow external manipulation" );
}
catch ( UnsupportedOperationException e )
{ // good
}
}
@Test
public void testRemoveUnloadedHeavyProperty()
{
/*
* Checks a bug where removing non-cached heavy properties
* would cause NPE in auto indexer.
*/
graphDb.index().getNodeAutoIndexer().setEnabled( true );
graphDb.index().getNodeAutoIndexer().startAutoIndexingProperty(
"nodeProp" );
newTransaction();
Node node1 = graphDb.createNode();
// Large array, needed for making sure this is a heavy property
node1.setProperty( "nodeProp", new int[] { -1, 2, 3, 4, 5, 6, 1, 1, 1,
1 } );
newTransaction();
graphDb.getNodeManager().clearCache();
node1.removeProperty( "nodeProp" );
newTransaction();
assertFalse( node1.hasProperty( "nodeProp" ) );
}
}