use of org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException in project neo4j by neo4j.
the class ConstraintIndexConcurrencyTest method shouldNotAllowConcurrentViolationOfConstraint.
@Test
void shouldNotAllowConcurrentViolationOfConstraint() throws Exception {
// Given
Label label = label("Foo");
String propertyKey = "bar";
String conflictingValue = "baz";
String constraintName = "MyConstraint";
// a constraint
try (Transaction tx = db.beginTx()) {
tx.schema().constraintFor(label).assertPropertyIsUnique(propertyKey).withName(constraintName).create();
tx.commit();
}
// When
try (Transaction tx = db.beginTx()) {
KernelTransaction ktx = ((InternalTransaction) tx).kernelTransaction();
int labelId = ktx.tokenRead().nodeLabel(label.name());
int propertyKeyId = ktx.tokenRead().propertyKey(propertyKey);
Read read = ktx.dataRead();
try (NodeValueIndexCursor cursor = ktx.cursors().allocateNodeValueIndexCursor(ktx.cursorContext(), ktx.memoryTracker())) {
IndexDescriptor index = ktx.schemaRead().indexGetForName(constraintName);
IndexReadSession indexSession = ktx.dataRead().indexReadSession(index);
read.nodeIndexSeek(indexSession, cursor, unconstrained(), PropertyIndexQuery.exact(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()) {
transaction.createNode(label).setProperty(propertyKey, conflictingValue);
transaction.commit();
}
return null;
}, db).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 = ktx.dataWrite().nodeCreate();
ktx.dataWrite().nodeAddLabel(node, labelId);
var e = assertThrows(UniquePropertyValueValidationException.class, () -> ktx.dataWrite().nodeSetProperty(node, propertyKeyId, Values.of(conflictingValue)));
assertEquals(ConstraintDescriptorFactory.uniqueForLabel(labelId, propertyKeyId), e.constraint());
IndexEntryConflictException conflict = Iterators.single(e.conflicts().iterator());
assertEquals(Values.stringValue(conflictingValue), conflict.getSinglePropertyValue());
tx.commit();
}
}
use of org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException in project neo4j by neo4j.
the class UniquePropertyValueValidationException method getUserMessage.
@Override
public String getUserMessage(TokenNameLookup tokenNameLookup) {
SchemaDescriptor schema = constraint.schema();
StringBuilder message = new StringBuilder();
for (Iterator<IndexEntryConflictException> iterator = conflicts.iterator(); iterator.hasNext(); ) {
IndexEntryConflictException conflict = iterator.next();
message.append(conflict.evidenceMessage(tokenNameLookup, schema));
if (iterator.hasNext()) {
message.append(System.lineSeparator());
}
}
return message.toString();
}
use of org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException in project neo4j by neo4j.
the class TentativeConstraintIndexProxy method verifyDeferredConstraints.
@Override
public void verifyDeferredConstraints(NodePropertyAccessor accessor) throws IndexEntryConflictException, IOException {
// If we've seen constraint violation failures in here when updates came in then fail immediately with those
if (!failures.isEmpty()) {
Iterator<IndexEntryConflictException> failureIterator = failures.iterator();
IndexEntryConflictException conflict = failureIterator.next();
failureIterator.forEachRemaining(conflict::addSuppressed);
throw conflict;
}
// Otherwise consolidate the usual verification
super.verifyDeferredConstraints(accessor);
}
use of org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException in project neo4j by neo4j.
the class ConstraintIndexCreator method createUniquenessConstraintIndex.
/**
* You MUST hold a label write lock before you call this method.
* However the label write lock is temporarily released while populating the index backing the constraint.
* It goes a little like this:
* <ol>
* <li>Prerequisite: Getting here means that there's an open schema transaction which has acquired the
* LABEL WRITE lock.</li>
* <li>Index schema rule which is backing the constraint is created in a nested mini-transaction
* which doesn't acquire any locking, merely adds tx state and commits so that the index rule is applied
* to the store, which triggers the index population</li>
* <li>Release the LABEL WRITE lock</li>
* <li>Await index population to complete</li>
* <li>Acquire the LABEL WRITE lock (effectively blocking concurrent transactions changing
* data related to this constraint, and it so happens, most other transactions as well) and verify
* the uniqueness of the built index</li>
* <li>Leave this method, knowing that the uniqueness constraint rule will be added to tx state
* and this tx committed, which will create the uniqueness constraint</li>
* </ol>
*/
public IndexDescriptor createUniquenessConstraintIndex(KernelTransactionImplementation transaction, IndexBackedConstraintDescriptor constraint, IndexPrototype prototype) throws TransactionFailureException, CreateConstraintFailureException, UniquePropertyValueValidationException, AlreadyConstrainedException {
String constraintString = constraint.userDescription(transaction.tokenRead());
log.info("Starting constraint creation: %s.", constraintString);
IndexDescriptor index;
SchemaRead schemaRead = transaction.schemaRead();
try {
index = checkAndCreateConstraintIndex(schemaRead, transaction.tokenRead(), constraint, prototype);
} catch (AlreadyConstrainedException e) {
throw e;
} catch (KernelException e) {
throw new CreateConstraintFailureException(constraint, e);
}
boolean success = false;
boolean reacquiredLabelLock = false;
Client locks = transaction.lockClient();
ResourceType keyType = constraint.schema().keyType();
long[] lockingKeys = constraint.schema().lockingKeys();
try {
locks.acquireShared(transaction.lockTracer(), keyType, lockingKeys);
IndexProxy proxy = indexingService.getIndexProxy(index);
// Release the LABEL WRITE lock during index population.
// At this point the integrity of the constraint to be created was checked
// while holding the lock and the index rule backing the soon-to-be-created constraint
// has been created. Now it's just the population left, which can take a long time
locks.releaseExclusive(keyType, lockingKeys);
awaitConstraintIndexPopulation(constraint, proxy, transaction);
log.info("Constraint %s populated, starting verification.", constraintString);
// Index population was successful, but at this point we don't know if the uniqueness constraint holds.
// Acquire LABEL WRITE lock and verify the constraints here in this user transaction
// and if everything checks out then it will be held until after the constraint has been
// created and activated.
locks.acquireExclusive(transaction.lockTracer(), keyType, lockingKeys);
reacquiredLabelLock = true;
try (NodePropertyAccessor propertyAccessor = new DefaultNodePropertyAccessor(transaction.newStorageReader(), transaction.cursorContext(), transaction.memoryTracker())) {
indexingService.getIndexProxy(index).verifyDeferredConstraints(propertyAccessor);
}
log.info("Constraint %s verified.", constraintString);
success = true;
return index;
} catch (IndexNotFoundKernelException e) {
String indexString = index.userDescription(transaction.tokenRead());
throw new TransactionFailureException(format("Index (%s) that we just created does not exist.", indexString), e);
} catch (IndexEntryConflictException e) {
throw new UniquePropertyValueValidationException(constraint, VERIFICATION, e, transaction.tokenRead());
} catch (InterruptedException | IOException e) {
throw new CreateConstraintFailureException(constraint, e);
} finally {
if (!success) {
if (!reacquiredLabelLock) {
locks.acquireExclusive(transaction.lockTracer(), keyType, lockingKeys);
}
if (indexStillExists(schemaRead, index)) {
dropUniquenessConstraintIndex(index);
}
}
}
}
use of org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException in project neo4j by neo4j.
the class ConstraintIndexCreatorTest method shouldDropIndexIfPopulationFails.
@Test
void shouldDropIndexIfPopulationFails() throws Exception {
// given
IndexingService indexingService = mock(IndexingService.class);
IndexProxy indexProxy = mock(IndexProxy.class);
when(indexingService.getIndexProxy(index)).thenReturn(indexProxy);
when(indexProxy.getDescriptor()).thenReturn(index);
when(schemaRead.indexGetForName(constraint.getName())).thenReturn(IndexDescriptor.NO_INDEX, index);
IndexEntryConflictException cause = new IndexEntryConflictException(2, 1, Values.of("a"));
doThrow(new IndexPopulationFailedKernelException("some index", cause)).when(indexProxy).awaitStoreScanCompleted(anyLong(), any());
when(schemaRead.index(any(SchemaDescriptor.class))).thenReturn(// first claim it doesn't exist, because it doesn't... so
Iterators.emptyResourceIterator()).thenReturn(// then after it failed claim it does exist
Iterators.iterator(index));
ConstraintIndexCreator creator = new ConstraintIndexCreator(() -> kernel, indexingService, logProvider);
// when
KernelTransactionImplementation transaction = createTransaction();
UniquePropertyValueValidationException exception = assertThrows(UniquePropertyValueValidationException.class, () -> creator.createUniquenessConstraintIndex(transaction, constraint, prototype));
assertEquals("Existing data does not satisfy Constraint( name='constraint', type='UNIQUENESS', schema=(:Label {prop}) ): " + "Both node 2 and node 1 share the property value ( String(\"a\") )", exception.getMessage());
assertEquals(2, kernel.transactions.size());
KernelTransactionImplementation tx1 = kernel.transactions.get(0);
verify(tx1).indexUniqueCreate(prototype);
verify(schemaRead, times(2)).indexGetForName(constraint.getName());
verifyNoMoreInteractions(schemaRead);
KernelTransactionImplementation kti2 = kernel.transactions.get(1);
verify(kti2).addIndexDoDropToTxState(index);
}
Aggregations