/* * Copyright (c) 2002-2016 "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 . */ package org.neo4j.concurrencytest; import org.junit.Rule; import org.junit.Test; import java.util.function.Supplier; import org.neo4j.graphdb.Label; import org.neo4j.graphdb.Transaction; import org.neo4j.kernel.api.Statement; import org.neo4j.kernel.api.exceptions.schema.UniquePropertyConstraintViolationKernelException; import org.neo4j.kernel.api.index.IndexDescriptor; import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge; import org.neo4j.kernel.internal.GraphDatabaseAPI; import org.neo4j.test.rule.DatabaseRule; import org.neo4j.test.rule.ImpermanentDatabaseRule; import org.neo4j.test.rule.concurrent.ThreadingRule; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.neo4j.graphdb.Label.label; import static org.neo4j.kernel.api.properties.Property.property; public class ConstraintIndexConcurrencyTest { @Rule public final DatabaseRule db = new ImpermanentDatabaseRule(); @Rule public final ThreadingRule threads = new ThreadingRule(); @Test public void shouldNotAllowConcurrentViolationOfConstraint() throws Exception { // Given GraphDatabaseAPI graphDb = db.getGraphDatabaseAPI(); Supplier statementSupplier = graphDb.getDependencyResolver() .resolveDependency( ThreadToStatementContextBridge.class ); Label label = label( "Foo" ); String propertyKey = "bar"; String conflictingValue = "baz"; // a constraint try ( Transaction tx = graphDb.beginTx() ) { graphDb.schema().constraintFor( label ).assertPropertyIsUnique( propertyKey ).create(); tx.success(); } // When try ( Transaction tx = graphDb.beginTx() ) { // create a statement and perform a lookup Statement statement = statementSupplier.get(); int labelId = statement.readOperations().labelGetForName( label.name() ); int propertyKeyId = statement.readOperations().propertyKeyGetForName( propertyKey ); statement.readOperations().nodesGetFromIndexSeek( new IndexDescriptor( labelId, propertyKeyId ), "The value is irrelevant, we just want to perform some sort of lookup against this index" ); // then let another thread come in and create a node threads.execute( db -> { try ( Transaction transaction = db.beginTx() ) { db.createNode( label ).setProperty( propertyKey, conflictingValue ); transaction.success(); } return null; }, graphDb ).get(); // before we create a node with the same property ourselves - using the same statement that we have // already used for lookup against that very same index long node = statement.dataWriteOperations().nodeCreate(); statement.dataWriteOperations().nodeAddLabel( node, labelId ); try { statement.dataWriteOperations().nodeSetProperty( node, property( propertyKeyId, conflictingValue ) ); fail( "exception expected" ); } // Then catch ( UniquePropertyConstraintViolationKernelException e ) { assertEquals( labelId, e.labelId() ); assertEquals( propertyKeyId, e.propertyKeyId() ); assertEquals( conflictingValue, e.propertyValue() ); } tx.success(); } } }