/** * 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 static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.neo4j.index.Neo4jTestCase.assertContains; import static org.neo4j.index.impl.lucene.Contains.contains; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.test.ImpermanentGraphDatabase; public class TestIndexDeletion { private static final String INDEX_NAME = "index"; private static GraphDatabaseService graphDb; private Index<Node> index; private Transaction tx; private String key; private Node node; private String value; private List<WorkThread> workers; @BeforeClass public static void setUpStuff() { graphDb = new ImpermanentGraphDatabase(); } @AfterClass public static void tearDownStuff() { graphDb.shutdown(); } @After public void commitTx() throws Exception { finishTx( true ); for ( WorkThread worker : workers ) { worker.rollback(); worker.die(); worker.shutdown(); } } public void rollbackTx() { finishTx( false ); } public void finishTx( boolean success ) { if ( tx != null ) { if ( success ) { tx.success(); } tx.finish(); tx = null; } } @Before public void createInitialData() { beginTx(); index = graphDb.index().forNodes( INDEX_NAME ); index.delete(); restartTx(); index = graphDb.index().forNodes( INDEX_NAME ); key = "key"; value = "my own value"; node = graphDb.createNode(); index.add( node, key, value ); workers = new ArrayList<WorkThread>(); } public void beginTx() { if ( tx == null ) { tx = graphDb.beginTx(); } } void restartTx() { finishTx( true ); beginTx(); } @Test public void shouldBeAbleToDeleteAndRecreateIndex() { restartTx(); assertContains( index.query( key, "own" ) ); index.delete(); restartTx(); Index<Node> recreatedIndex = graphDb.index().forNodes( INDEX_NAME, LuceneIndexImplementation.FULLTEXT_CONFIG ); assertNull( recreatedIndex.get( key, value ).getSingle() ); recreatedIndex.add( node, key, value ); restartTx(); assertContains( recreatedIndex.query( key, "own" ), node ); recreatedIndex.delete(); } @Test public void shouldNotBeDeletedWhenDeletionRolledBack() { restartTx(); index.delete(); rollbackTx(); index.get( key, value ); } @Test( expected = IllegalStateException.class ) public void shouldThrowIllegalStateForActionsAfterDeletedOnIndex() { restartTx(); index.delete(); restartTx(); index.query( key, "own" ); } @Test( expected = IllegalStateException.class ) public void shouldThrowIllegalStateForActionsAfterDeletedOnIndex2() { restartTx(); index.delete(); restartTx(); index.add( node, key, value ); } @Test( expected = IllegalStateException.class ) public void shouldThrowIllegalStateForActionsAfterDeletedOnIndex3() { restartTx(); index.delete(); index.query( key, "own" ); } @Test( expected = IllegalStateException.class ) public void shouldThrowIllegalStateForActionsAfterDeletedOnIndex4() { restartTx(); index.delete(); Index<Node> newIndex = graphDb.index().forNodes( INDEX_NAME ); newIndex.query( key, "own" ); } @Test public void deleteInOneTxShouldNotAffectTheOther() throws Exception { index.delete(); WorkThread firstTx = createWorker( "Single" ); firstTx.beginTransaction(); firstTx.createNodeAndIndexBy( key, "another value" ); firstTx.commit(); } @Test public void deleteAndCommitShouldBePublishedToOtherTransaction2() throws Exception { WorkThread firstTx = createWorker( "First" ); WorkThread secondTx = createWorker( "Second" ); firstTx.beginTransaction(); secondTx.beginTransaction(); firstTx.createNodeAndIndexBy(key, "some value"); secondTx.createNodeAndIndexBy(key, "some other value"); firstTx.deleteIndex(); firstTx.commit(); try { secondTx.queryIndex(key, "some other value"); fail( "Should throw exception" ); } catch ( Exception e ) { /* Good */ } secondTx.rollback(); // Since $Before will start a tx, add a value and keep tx open and // workers will delete the index so this test will fail in @After // if we don't rollback this tx rollbackTx(); } @Test public void indexDeletesShouldNotByVisibleUntilCommit() throws Exception { commitTx(); WorkThread firstTx = createWorker( "First" ); WorkThread secondTx = createWorker( "Second" ); firstTx.beginTransaction(); firstTx.removeFromIndex( key, value ); IndexHits<Node> indexHits = secondTx.queryIndex( key, value ); assertThat( indexHits, contains( node ) ); firstTx.rollback(); } @Test public void canDeleteIndexEvenIfEntitiesAreFoundToBeAbandonedInTheSameTx() throws Exception { // create and index a node Index<Node> nodeIndex = graphDb.index().forNodes( "index" ); Node node = graphDb.createNode(); nodeIndex.add( node, "key", "value" ); // make sure to commit the creation of the entry restartTx(); // delete the node to abandon the index entry node.delete(); restartTx(); // iterate over all nodes indexed with the key to discover abandoned for ( @SuppressWarnings( "unused" ) Node hit : nodeIndex.get( "key", "value" ) ); nodeIndex.delete(); restartTx(); } private WorkThread createWorker( String name ) { WorkThread workThread = new WorkThread( index, graphDb, node ); workers.add( workThread ); return workThread; } }