Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Takes read locks while matching on the database

Decorates the SPI, taking read locks every time a graph element is passed through the SPI. We also wrap the result iterator, taking care to close it when QueryContext once the iterator is empty, so the read locks can be released.
  • Loading branch information...
commit 2d17082295a3ccc27978be700e520b5775be0108 1 parent d210dc7
@systay authored
View
76 cypher/src/main/java/org/neo4j/cypher/internal/spi/gdsimpl/GDSBackedLocker.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2002-2012 "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.cypher.internal.spi.gdsimpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.neo4j.graphdb.Lock;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.PropertyContainer;
+import org.neo4j.graphdb.Relationship;
+import org.neo4j.graphdb.Transaction;
+
+public class GDSBackedLocker implements RepeatableReadQueryContext.Locker
+{
+ private final Transaction transaction;
+ private final Map<Long, Lock> nodeLocks = new HashMap<Long, Lock>();
+ private final Map<Long, Lock> relationshipLocks = new HashMap<Long, Lock>();
+
+ public GDSBackedLocker( Transaction transaction )
+ {
+ this.transaction = transaction;
+ }
+
+ @Override
+ public void readLock( PropertyContainer pc )
+ {
+ if ( pc instanceof Node )
+ {
+ lock( pc, ((Node) pc).getId(), nodeLocks );
+ } else
+ {
+ lock( pc, ((Relationship) pc).getId(), relationshipLocks );
+ }
+ }
+
+ private void lock( PropertyContainer pc, long id, Map<Long, Lock> lockHolder )
+ {
+ if ( !lockHolder.containsKey( id ) )
+ {
+ Lock lock = transaction.acquireReadLock( pc );
+ relationshipLocks.put( id, lock );
+ }
+ }
+
+ @Override
+ public void releaseAllReadLocks()
+ {
+ for(Lock lock : nodeLocks.values())
+ {
+ lock.release();
+ }
+
+ for(Lock lock : relationshipLocks.values())
+ {
+ lock.release();
+ }
+ }
+}
View
166 cypher/src/main/java/org/neo4j/cypher/internal/spi/gdsimpl/RepeatableReadQueryContext.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (c) 2002-2012 "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.cypher.internal.spi.gdsimpl;
+
+import org.neo4j.cypher.internal.spi.QueryContext;
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.PropertyContainer;
+import org.neo4j.graphdb.Relationship;
+import org.neo4j.helpers.collection.IterableWrapper;
+
+/**
+ * This QueryContext is responsible for taking read locks for all operations that read from the database.
+ * <p/>
+ * The close() method will then release all locks.
+ */
+public class RepeatableReadQueryContext implements QueryContext
+{
+
+ public interface Locker
+ {
+ void readLock( PropertyContainer pc );
+
+ void releaseAllReadLocks();
+ }
+
+ private final QueryContext inner;
+ private final Locker locker;
+ private final Operations<Node> nodeOps;
+ private final Operations<Relationship> relOps;
+
+ public RepeatableReadQueryContext( QueryContext inner, Locker locker )
+ {
+ this.inner = inner;
+ this.locker = locker;
+ this.nodeOps = new LockingOperations<Node>( inner.nodeOps() );
+ this.relOps = new LockingOperations<Relationship>( inner.relationshipOps() );
+ }
+
+ @Override
+ public Operations<Node> nodeOps()
+ {
+ return nodeOps;
+ }
+
+ @Override
+ public Operations<Relationship> relationshipOps()
+ {
+ return relOps;
+ }
+
+ @Override
+ public Node createNode()
+ {
+ return inner.createNode();
+ }
+
+ @Override
+ public Relationship createRelationship( Node start, Node end, String relType )
+ {
+ return inner.createRelationship( start, end, relType );
+ }
+
+ @Override
+ public Iterable<Relationship> getRelationshipsFor( Node node, Direction dir, String... types )
+ {
+ locker.readLock( node );
+ Iterable<Relationship> iter = inner.getRelationshipsFor( node, dir, types );
+ return new LockingIterator( iter );
+ }
+
+ @Override
+ public void close()
+ {
+ locker.releaseAllReadLocks();
+ }
+
+ private class LockingIterator extends IterableWrapper<Relationship, Relationship>
+ {
+ public LockingIterator( Iterable<Relationship> iterableToWrap )
+ {
+ super( iterableToWrap );
+ }
+
+ @Override
+ protected Relationship underlyingObjectToObject( Relationship rel )
+ {
+ locker.readLock( rel );
+ return rel;
+ }
+ }
+
+ private class LockingOperations<T extends PropertyContainer> implements Operations<T>
+ {
+ private final Operations<T> inner;
+
+ private LockingOperations( Operations<T> inner )
+ {
+ this.inner = inner;
+ }
+
+ @Override
+ public void delete( T obj )
+ {
+ inner.delete( obj );
+ }
+
+ @Override
+ public void setProperty( T obj, String propertyKey, Object value )
+ {
+ inner.setProperty( obj, propertyKey, value );
+ }
+
+ @Override
+ public void removeProperty( T obj, String propertyKey )
+ {
+ inner.removeProperty( obj, propertyKey );
+ }
+
+ @Override
+ public Object getProperty( T obj, String propertyKey )
+ {
+ locker.readLock( obj );
+ return inner.getProperty( obj, propertyKey );
+ }
+
+ @Override
+ public boolean hasProperty( T obj, String propertyKey )
+ {
+ locker.readLock( obj );
+ return inner.hasProperty( obj, propertyKey );
+ }
+
+ @Override
+ public Iterable<String> propertyKeys( T obj )
+ {
+ locker.readLock( obj );
+ return inner.propertyKeys( obj );
+ }
+
+ @Override
+ public T getById( long id )
+ {
+ T obj = inner.getById( id );
+ locker.readLock( obj );
+ return obj;
+ }
+ }
+}
View
94 cypher/src/main/scala/org/neo4j/cypher/internal/ClosingIterator.scala
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2002-2012 "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.cypher.internal
+
+import spi.QueryContext
+import org.neo4j.graphdb.{TransactionFailureException, Transaction}
+import org.neo4j.kernel.impl.nioneo.store.ConstraintViolationException
+import org.neo4j.cypher.NodeStillHasRelationshipsException
+
+/**
+ * An iterator that decorates an inner iterator, and calls close() on the QueryContext once
+ * the inner iterator is empty.
+ */
+class ClosingIterator[T](inner: Iterator[T], queryContext: QueryContext, tx: Transaction) extends Iterator[T] {
+ private var closed: Boolean = false
+ lazy val still_has_relationships = "Node record Node\\[(\\d),.*] still has relationships".r
+
+ def hasNext: Boolean = failIfThrows {
+ val innerHasNext: Boolean = inner.hasNext
+ if (!innerHasNext) {
+ close()
+ }
+ innerHasNext
+ }
+
+
+ def next(): T = failIfThrows {
+ val result: T = inner.next()
+ if (!inner.hasNext) {
+ close()
+ }
+ result
+ }
+
+ private def close() {
+ translateException {
+ if (!closed) {
+ closed = true
+ queryContext.close()
+ }
+ tx.success()
+ tx.finish()
+ }
+ }
+
+ private def translateException[U](f: => U): U = try {
+ f
+ } catch {
+ case e: TransactionFailureException => {
+
+ var cause:Throwable = e
+ while(cause.getCause != null)
+ {
+ cause = cause.getCause
+ if(cause.isInstanceOf[ConstraintViolationException])
+ {
+ cause.getMessage match {
+ case still_has_relationships(id) => throw new NodeStillHasRelationshipsException(id.toLong, e)
+ case _ => throw e
+ }
+ }
+ }
+
+ throw e
+ }
+ }
+
+
+ private def failIfThrows[U](f: => U): U = try {
+ f
+ } catch {
+ case t: Throwable if !closed =>
+ tx.failure()
+ tx.finish()
+ throw t
+ }
+}
View
13 cypher/src/main/scala/org/neo4j/cypher/internal/commands/expressions/StringFunctions.scala
@@ -21,6 +21,7 @@ package org.neo4j.cypher.internal.commands.expressions
import org.neo4j.cypher.CypherTypeException
import scala.collection.JavaConverters._
+import collection.Map
import org.neo4j.cypher.internal.helpers.{IsCollection, CollectionSupport}
import org.neo4j.graphdb.{PropertyContainer, Relationship, Node}
import org.neo4j.cypher.internal.symbols._
@@ -132,8 +133,8 @@ case class SubstringFunction(orig: Expression, start: Expression, length: Expres
def calculateType(symbols: SymbolTable) = StringType()
def symbolTableDependencies = orig.symbolTableDependencies ++
- start.symbolTableDependencies ++
- length.symbolTableDependencies
+ start.symbolTableDependencies ++
+ length.symbolTableDependencies
}
case class ReplaceFunction(orig: Expression, search: Expression, replaceWith: Expression) extends NullInNullOutExpression(orig) with StringHelper {
@@ -164,8 +165,8 @@ case class ReplaceFunction(orig: Expression, search: Expression, replaceWith: Ex
def calculateType(symbols: SymbolTable) = StringType()
def symbolTableDependencies = orig.symbolTableDependencies ++
- search.symbolTableDependencies ++
- replaceWith.symbolTableDependencies
+ search.symbolTableDependencies ++
+ replaceWith.symbolTableDependencies
}
case class LeftFunction(orig: Expression, length: Expression) extends NullInNullOutExpression(orig) with StringHelper with NumericHelper {
@@ -193,7 +194,7 @@ case class LeftFunction(orig: Expression, length: Expression) extends NullInNull
def calculateType(symbols: SymbolTable) = StringType()
def symbolTableDependencies = orig.symbolTableDependencies ++
- length.symbolTableDependencies
+ length.symbolTableDependencies
}
case class RightFunction(orig: Expression, length: Expression) extends NullInNullOutExpression(orig) with StringHelper with NumericHelper {
@@ -221,5 +222,5 @@ case class RightFunction(orig: Expression, length: Expression) extends NullInNul
def calculateType(symbols: SymbolTable) = StringType()
def symbolTableDependencies = orig.symbolTableDependencies ++
- length.symbolTableDependencies
+ length.symbolTableDependencies
}
View
25 cypher/src/main/scala/org/neo4j/cypher/internal/executionplan/ExecutionPlanImpl.scala
@@ -22,8 +22,9 @@ package org.neo4j.cypher.internal.executionplan
import builders._
import org.neo4j.cypher.internal.pipes._
import org.neo4j.cypher._
+import internal.ClosingIterator
import internal.commands._
-import internal.spi.gdsimpl.GDSBackedQueryContext
+import internal.spi.gdsimpl.{GDSBackedLocker, RepeatableReadQueryContext, GDSBackedQueryContext}
import internal.symbols.{NodeType, RelationshipType, SymbolTable}
import org.neo4j.kernel.InternalAbstractGraphDatabase
import org.neo4j.graphdb.GraphDatabaseService
@@ -135,18 +136,28 @@ class ExecutionPlanImpl(inputQuery: Query, graph: GraphDatabaseService) extends
}
private def prepareStateAndResult(params: Map[String, Any], pipe: Pipe): (QueryState, Iterator[ExecutionContext]) = {
- val gdsContext = new GDSBackedQueryContext(graph)
- val state = new QueryState(graph, gdsContext, params)
+ val tx = graph.beginTx()
+
+ try
+ {val gdsContext = new GDSBackedQueryContext(graph)
+ val lockingContext = new RepeatableReadQueryContext(gdsContext, new GDSBackedLocker(tx))
+ val state = new QueryState(graph, lockingContext, params)
val results = pipe.createResults(state)
- (state, results)
- }
+ val closingIterator = new ClosingIterator[ExecutionContext](results, state.query, tx)
+ (state, closingIterator)
+ } catch {
+ case e: Throwable =>
+ tx.failure()
+ tx.finish()
+ throw e
+ }
+ }
private def getEagerReadWriteQuery(pipe: Pipe, columns: List[String]): Map[String, Any] => ExecutionResult = {
val func = (params: Map[String, Any]) => {
- val commitPipe = new CommitPipe(pipe, graph)
- val (state, results) = prepareStateAndResult(params, commitPipe)
+ val (state, results) = prepareStateAndResult(params, pipe)
new EagerPipeExecutionResult(results, columns, state, graph)
}
View
79 cypher/src/main/scala/org/neo4j/cypher/internal/pipes/CommitPipe.scala
@@ -1,79 +0,0 @@
-/**
- * Copyright (c) 2002-2012 "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.cypher.internal.pipes
-
-import org.neo4j.graphdb.{Transaction, TransactionFailureException, GraphDatabaseService}
-import org.neo4j.kernel.impl.nioneo.store.{ConstraintViolationException, InvalidRecordException}
-import org.neo4j.cypher.{NodeStillHasRelationshipsException, InternalException}
-import org.neo4j.cypher.internal.symbols.SymbolTable
-
-class CommitPipe(source: Pipe, graph: GraphDatabaseService) extends PipeWithSource(source) {
- lazy val still_has_relationships = "Node record Node\\[(\\d),.*] still has relationships".r
-
- def createResults(state: QueryState) = {
- lazy val tx = state.transaction match {
- case None => throw new InternalException("Expected to be in a transaction but wasn't")
- case Some(tx : Transaction) => tx
- }
- try {
- try {
- val result = source.createResults(state).toList.iterator
- tx.success()
- result
- } catch {
- case e: Throwable => {
- tx.failure()
- throw e
- }
- } finally {
- tx.finish()
- }
- } catch {
- case e: TransactionFailureException => {
-
- var cause:Throwable = e
- while(cause.getCause != null)
- {
- cause = cause.getCause
- if(cause.isInstanceOf[ConstraintViolationException])
- {
- cause.getMessage match {
- case still_has_relationships(id) => throw new NodeStillHasRelationshipsException(id.toLong, e)
- case _ => throw e
- }
- }
- }
-
- throw e
- }
- }
- }
-
- def executionPlan() = source.executionPlan() + "\r\nTransactionBegin()"
-
-// def symbols = source.symbols
- def symbols = source.symbols
-
- def dependencies = Seq()
-
- def deps = Map()
-
- def assertTypes(symbols: SymbolTable) {}
-}
View
143 cypher/src/test/java/org/neo4j/cypher/internal/spi/QueryContextContract.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2002-2012 "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/>.
+ */
+///**
+//* Copyright (c) 2002-2012 "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/>.
+//*/
+///**
+//* Copyright (c) 2002-2012 "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.cypher.internal.spi;
+//
+//import static org.hamcrest.CoreMatchers.is;
+//import static org.hamcrest.CoreMatchers.not;
+//import static org.hamcrest.CoreMatchers.nullValue;
+//import static org.junit.Assert.*;
+//import static org.neo4j.cypher.internal.spi.Direction.INCOMING;
+//import static org.neo4j.cypher.internal.spi.Direction.OUTGOING;
+//
+//import java.util.Iterator;
+//
+//import org.junit.Test;
+//
+//public abstract class QueryContextContract
+//{
+//
+// public abstract QueryContext ctx();
+//
+// @Test
+// public void shouldCreateNode() throws Exception
+// {
+// // When
+// Long nodeId = ctx().createNode();
+//
+// // Then
+// assertThat(nodeId, not(nullValue()));
+// }
+//
+// @Test
+// public void shouldSetAndGetNodeProperties() throws Exception
+// {
+// // Given
+// long nodeId = ctx().createNode();
+// int propertyKeyId = ctx().getOrCreatePropertyKeyId( "name" );
+//
+// // When
+// ctx().setNodeProperty( nodeId, propertyKeyId, "BOB!!" );
+//
+// // Then
+// assertThat( (String) ctx().getNodeProperty( nodeId, propertyKeyId ), is( "BOB!!" ) );
+// }
+//
+// @Test
+// public void shouldCreateRelationship() throws Exception
+// {
+// // Given
+// long node1 = ctx().createNode();
+// long node2 = ctx().createNode();
+//
+// int type = ctx().getOrCreateRelationshipTypeId( "KNOWS" );
+//
+// // When
+// long relId = ctx().createRelationship( node1, node2, type );
+//
+// // Then
+// Iterator<Long> rels1 = ctx().getRelationshipsFor( node1, OUTGOING, type );
+// assertThat( rels1.hasNext(), is( true ) );
+// assertThat( rels1.next(), is( relId ) );
+//
+// Iterator<Long> rels2 = ctx().getRelationshipsFor( node2, INCOMING, type );
+// assertThat( rels2.hasNext(), is( true ) );
+// assertThat( rels2.next(), is( relId ) );
+// }
+//
+// @Test
+// public void shouldSetAndGetRelationshipProperties() throws Exception
+// {
+// // Given
+// long relId = ctx().createRelationship(
+// ctx().createNode(),
+// ctx().createNode(),
+// ctx().getOrCreateRelationshipTypeId( "A" ) );
+//
+// int propertyKeyId = ctx().getOrCreatePropertyKeyId( "name" );
+//
+// // When
+// ctx().setRelationshipProperty( relId, propertyKeyId, "BOB!!" );
+//
+// // Then
+// assertThat( (String) ctx().getRelationshipProperty( relId, propertyKeyId ), is( "BOB!!" ) );
+// }
+//
+//
+//
+//
+//}
View
123 cypher/src/test/java/org/neo4j/cypher/internal/spi/RepeatableReadQueryContextContract.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright (c) 2002-2012 "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.cypher.internal.spi;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.neo4j.graphdb.DynamicRelationshipType.withName;
+import static org.neo4j.helpers.collection.IteratorUtil.count;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.neo4j.cypher.internal.spi.gdsimpl.GDSBackedQueryContext;
+import org.neo4j.cypher.internal.spi.gdsimpl.RepeatableReadQueryContext;
+import org.neo4j.graphdb.Direction;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Relationship;
+import org.neo4j.graphdb.Transaction;
+import org.neo4j.test.ImpermanentGraphDatabase;
+
+public class RepeatableReadQueryContextContract
+{
+ private ImpermanentGraphDatabase database;
+ private QueryContext innerContext;
+ private Node node;
+ private RepeatableReadQueryContext.Locker locker;
+
+ @Before
+ public void init()
+ {
+ database = new ImpermanentGraphDatabase();
+ locker = mock( RepeatableReadQueryContext.Locker.class );
+ innerContext = new GDSBackedQueryContext( database );
+ Transaction tx = database.beginTx();
+ node = database.createNode();
+ Node b = database.createNode();
+ Node c = database.createNode();
+ node.createRelationshipTo( b, withName( "R" ) );
+ node.createRelationshipTo( c, withName( "R" ) );
+ tx.success();
+ tx.finish();
+ }
+
+ @Test
+ public void has_property_locks_node() throws Exception
+ {
+ // Given
+ Node node = createNode();
+ RepeatableReadQueryContext lockingContext = new RepeatableReadQueryContext( innerContext, locker );
+
+ //When
+ lockingContext.nodeOps().hasProperty( node, "foo" );
+
+ //Then
+ verify( locker ).readLock( node );
+ }
+
+ @Test
+ public void close_releases_locks() throws Exception
+ {
+ // Given
+ Node node = createNode();
+ RepeatableReadQueryContext lockingContext = new RepeatableReadQueryContext( innerContext, locker );
+
+ //When
+ lockingContext.nodeOps().hasProperty( node, "foo" );
+ lockingContext.close();
+
+ //Then
+ verify( locker ).readLock( node );
+ verify( locker ).releaseAllReadLocks();
+ }
+
+ @Test
+ public void get_relationships_locks_node_and_relationships() throws Exception
+ {
+ // Given
+ RepeatableReadQueryContext lockingContext = new RepeatableReadQueryContext( innerContext, locker );
+
+ //When
+ Iterable<Relationship> rels = lockingContext.getRelationshipsFor( node, Direction.OUTGOING );
+ int count_the_matching_rows = count( rels );
+ lockingContext.close();
+
+ //Then
+ verify( locker ).readLock( node );
+ for ( Relationship rel : rels )
+ {
+ //Relationship locked
+ verify( locker ).readLock( rel );
+ }
+ verify( locker ).releaseAllReadLocks();
+
+ assertThat( count_the_matching_rows, is( 2 ) );
+ }
+
+ private Node createNode()
+ {
+ Transaction tx = database.beginTx();
+ Node node = database.createNode();
+ tx.success();
+ tx.finish();
+ return node;
+ }
+}
View
92 cypher/src/test/java/org/neo4j/cypher/internal/spi/gdsimpl/GDSQueryContextContractIT.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2002-2012 "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/>.
+ */
+///**
+// * Copyright (c) 2002-2012 "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.cypher.internal.spi.gdsimpl;
+//
+//import org.junit.After;
+//import org.junit.AfterClass;
+//import org.junit.Before;
+//import org.junit.BeforeClass;
+//import org.neo4j.cypher.internal.spi.QueryContext;
+//import org.neo4j.cypher.internal.spi.QueryContextContract;
+//import org.neo4j.graphdb.Transaction;
+//import org.neo4j.test.ImpermanentGraphDatabase;
+//
+//public class GDSQueryContextContractIT extends QueryContextContract
+//{
+//
+// private static ImpermanentGraphDatabase gdb;
+// private QueryContext ctx;
+// private Transaction tx;
+//
+// @BeforeClass
+// public static void createDb()
+// {
+// gdb = new ImpermanentGraphDatabase( );
+// }
+//
+//
+// @Before
+// public void createQueryContext()
+// {
+// tx = gdb.beginTx();
+// ctx = new GDSBackedQueryContext( gdb );
+// }
+//
+// @After
+// public void clean()
+// {
+// ctx.close();
+// tx.finish();
+// }
+//
+// @AfterClass
+// public static void destroy()
+// {
+// gdb.shutdown();
+// }
+//
+//
+// @Override
+// public QueryContext ctx()
+// {
+// return ctx;
+// }
+//
+//
+//}
View
121 cypher/src/test/scala/org/neo4j/cypher/internal/ClosingIteratorTest.scala
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2002-2012 "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.cypher.internal
+
+import org.junit.Test
+import org.hamcrest.CoreMatchers.is
+import org.junit.Assert.assertThat
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.when
+import org.mockito.Mockito.verify
+import spi.QueryContext
+import org.neo4j.graphdb.Transaction
+import org.scalatest.Assertions
+
+class ClosingIteratorTest extends Assertions {
+ @Test
+ def should_call_close_when_we_reach_the_end() {
+ //Given
+ val queryContext = mock(classOf[QueryContext])
+ val tx = mock(classOf[Transaction])
+ val wrapee = Iterator(42)
+ val iterator = new ClosingIterator(wrapee, queryContext, tx)
+
+ //When
+ val result = iterator.next()
+
+ //Then
+ verify(queryContext).close()
+ assertThat(result, is(42))
+ }
+
+ @Test
+ def should_close_querycontext_even_for_empty_iterator() {
+ //Given
+ val queryContext = mock(classOf[QueryContext])
+ val tx = mock(classOf[Transaction])
+ val wrapee = Iterator.empty
+ val iterator = new ClosingIterator(wrapee, queryContext, tx)
+
+ //When
+ val result = iterator.hasNext
+
+ //Then
+ verify(queryContext).close()
+ assertThat(result, is(false))
+ }
+
+ @Test
+ def multiple_has_next_should_not_close_more_than_once() {
+ //Given
+ val queryContext = mock(classOf[QueryContext])
+ val tx = mock(classOf[Transaction])
+ val wrapee = Iterator.empty
+ val iterator = new ClosingIterator(wrapee, queryContext, tx)
+
+ //When
+ val result = iterator.hasNext
+ iterator.hasNext
+ iterator.hasNext
+ iterator.hasNext
+ iterator.hasNext
+
+ //Then
+ verify(queryContext).close()
+ assertThat(result, is(false))
+ }
+
+ @Test
+ def exception_in_hasNext_should_fail_transaction() {
+ //Given
+ val queryContext = mock(classOf[QueryContext])
+ val tx = mock(classOf[Transaction])
+
+ val wrapee = mock(classOf[Iterator[Int]])
+ when(wrapee.hasNext).thenThrow(new RuntimeException)
+
+ val iterator = new ClosingIterator(wrapee, queryContext, tx)
+
+ //When
+ intercept[RuntimeException](iterator.hasNext)
+
+ //Then
+ verify(tx).failure()
+ }
+
+ @Test
+ def exception_in_next_should_fail_transaction() {
+ //Given
+ val queryContext = mock(classOf[QueryContext])
+ val tx = mock(classOf[Transaction])
+
+ val wrapee = mock(classOf[Iterator[Int]])
+ when(wrapee.hasNext).thenReturn(true)
+ when(wrapee.next()).thenThrow(new RuntimeException)
+
+ val iterator = new ClosingIterator(wrapee, queryContext, tx)
+
+ //When
+ intercept[RuntimeException](iterator.next())
+
+ //Then
+ verify(tx).failure()
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.