From 892a2488ebdd6c2321d1eed920fdad32ab475868 Mon Sep 17 00:00:00 2001 From: Tobias Lindaaker Date: Thu, 23 Mar 2017 16:57:40 +0100 Subject: [PATCH] Cache the PropertyExistenceEnforcer Only create new instances of PropertyExistenceEnforcer when needed after the schema has changed. In addition to that we now only apply the PropertyExistenceEnforcer if there are data changes. --- .../kernel/impl/api/store/SchemaCache.java | 12 +- .../kernel/impl/api/store/StorageLayer.java | 7 + .../recordstorage/RecordStorageEngine.java | 2 +- .../storageengine/api/StoreReadLayer.java | 3 + .../EnterpriseConstraintSemantics.java | 54 +-- .../enterprise/PropertyExistenceEnforcer.java | 449 +++++++++++++----- .../PropertyExistenceEnforcerMergeTest.java | 148 ++++++ 7 files changed, 516 insertions(+), 159 deletions(-) create mode 100644 enterprise/kernel/src/test/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcerMergeTest.java diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java index d0667b7cc9e91..e0fd7f62e6049 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/SchemaCache.java @@ -25,12 +25,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import org.neo4j.helpers.collection.Iterators; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; import org.neo4j.kernel.api.schema_new.SchemaDescriptor; import org.neo4j.kernel.api.schema_new.SchemaDescriptorPredicates; -import org.neo4j.kernel.api.schema_new.constaints.ConstraintBoundary; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.impl.constraints.ConstraintSemantics; @@ -56,6 +57,8 @@ public class SchemaCache private final Map indexDescriptors = new HashMap<>(); private final ConstraintSemantics constraintSemantics; + private final Map,Object> dependantState = new ConcurrentHashMap<>(); + public SchemaCache( ConstraintSemantics constraintSemantics, Iterable initialRules ) { this.constraintSemantics = constraintSemantics; @@ -128,8 +131,14 @@ public Iterator constraintsForSchema( SchemaDescriptor des return Iterators.filter( SchemaDescriptor.equalTo( descriptor ), constraints.iterator() ); } + public T getOrCreateDependantState( Class type, Function factory, P parameter ) + { + return type.cast( dependantState.computeIfAbsent( type, key -> factory.apply( parameter ) ) ); + } + public void addSchemaRule( SchemaRule rule ) { + dependantState.clear(); if ( rule instanceof ConstraintRule ) { ConstraintRule constraintRule = (ConstraintRule) rule; @@ -163,6 +172,7 @@ public void load( List schemaRuleIterator ) public void removeSchemaRule( long id ) { + dependantState.clear(); if ( constraintRuleById.containsKey( id ) ) { ConstraintRule rule = constraintRuleById.remove( id ); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorageLayer.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorageLayer.java index de175856b9417..525c9a6bbf315 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorageLayer.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/store/StorageLayer.java @@ -20,6 +20,7 @@ package org.neo4j.kernel.impl.api.store; import java.util.Iterator; +import java.util.function.Function; import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.function.Supplier; @@ -610,6 +611,12 @@ public int degreeRelationshipsInGroup( StorageStatement storeStatement, long nod relationshipGroupRecord, storeStatement.recordCursors() ); } + @Override + public T getOrCreateSchemaDependantState( Class type, Function factory ) + { + return schemaCache.getOrCreateDependantState( type, factory, this ); + } + private void visitNode( StorageStatement statement, NodeItem nodeItem, DegreeVisitor visitor ) { try ( Cursor relationships = nodeGetRelationships( statement, nodeItem, Direction.BOTH ) ) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordStorageEngine.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordStorageEngine.java index c61e9b04ca546..d0ee182af1870 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordStorageEngine.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordStorageEngine.java @@ -322,7 +322,7 @@ public void createCommands( txStateVisitor, storeLayer, storageStatement, txState, countsRecordState ); try ( TxStateVisitor visitor = txStateVisitor ) { - txState.accept( txStateVisitor ); + txState.accept( visitor ); } // Convert record state into commands diff --git a/community/kernel/src/main/java/org/neo4j/storageengine/api/StoreReadLayer.java b/community/kernel/src/main/java/org/neo4j/storageengine/api/StoreReadLayer.java index 8843e8a9db9bc..82f617886e8bc 100644 --- a/community/kernel/src/main/java/org/neo4j/storageengine/api/StoreReadLayer.java +++ b/community/kernel/src/main/java/org/neo4j/storageengine/api/StoreReadLayer.java @@ -20,6 +20,7 @@ package org.neo4j.storageengine.api; import java.util.Iterator; +import java.util.function.Function; import java.util.function.IntPredicate; import org.neo4j.collection.primitive.PrimitiveIntIterator; @@ -384,4 +385,6 @@ DoubleLongRegister indexSample( LabelSchemaDescriptor descriptor, DoubleLongRegi int degreeRelationshipsInGroup( StorageStatement storeStatement, long id, long groupId, Direction direction, Integer relType ); + + T getOrCreateSchemaDependantState( Class type, Function factory ); } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/EnterpriseConstraintSemantics.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/EnterpriseConstraintSemantics.java index 0630df763a1de..952b6c643895e 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/EnterpriseConstraintSemantics.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/EnterpriseConstraintSemantics.java @@ -19,9 +19,7 @@ */ package org.neo4j.kernel.impl.enterprise; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; import java.util.function.BiPredicate; import org.neo4j.cursor.Cursor; @@ -31,7 +29,6 @@ import org.neo4j.kernel.api.exceptions.schema.RelationshipPropertyExistenceException; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; import org.neo4j.kernel.api.schema_new.RelationTypeSchemaDescriptor; -import org.neo4j.kernel.api.schema_new.SchemaProcessor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.constaints.NodeKeyConstraintDescriptor; import org.neo4j.kernel.impl.constraints.StandardConstraintSemantics; @@ -43,6 +40,7 @@ import org.neo4j.storageengine.api.txstate.TxStateVisitor; import static org.neo4j.kernel.api.exceptions.schema.ConstraintValidationException.Phase.VERIFICATION; +import static org.neo4j.kernel.impl.enterprise.PropertyExistenceEnforcer.getOrCreatePropertyExistenceEnforcerFrom; public class EnterpriseConstraintSemantics extends StandardConstraintSemantics { @@ -135,48 +133,16 @@ private CreateConstraintFailureException createConstraintFailure( ConstraintVali public TxStateVisitor decorateTxStateVisitor( StoreReadLayer storeLayer, ReadableTransactionState txState, TxStateVisitor visitor ) { - ExistenceConstraintCollector collector = new ExistenceConstraintCollector(); - Iterator constraints = storeLayer.constraintsGetAll(); - while ( constraints.hasNext() ) + if ( !txState.hasDataChanges() ) { - collector.addIfRelevant( constraints.next() ); - } - - if ( collector.hasExistenceConstraints() ) - { - return new PropertyExistenceEnforcer( visitor, txState, storeLayer, collector.labelExists, collector.relTypeExists ); - } - return visitor; - } - - private static class ExistenceConstraintCollector implements SchemaProcessor - { - final List labelExists = new ArrayList<>(); - final List relTypeExists = new ArrayList<>(); - - void addIfRelevant( ConstraintDescriptor constraint ) - { - if ( constraint.enforcesPropertyExistence() ) - { - constraint.schema().processWith( this ); - } - } - - @Override - public void processSpecific( LabelSchemaDescriptor schema ) - { - labelExists.add( schema ); - } - - @Override - public void processSpecific( RelationTypeSchemaDescriptor schema ) - { - relTypeExists.add( schema ); - } - - boolean hasExistenceConstraints() - { - return labelExists.size() > 0 || relTypeExists.size() > 0; + // If there are no data changes, there is no need to enforce constraints. Since there is no need to + // enforce constraints, there is no need to build up the state required to be able to enforce constraints. + // In fact, it might even be counter productive to build up that state, since if there are no data changes + // there would be schema changes instead, and in that case we would throw away the schema-dependant state + // we just built when the schema changing transaction commits. + return visitor; } + return getOrCreatePropertyExistenceEnforcerFrom( storeLayer ) + .decorate( visitor, txState, storeLayer ); } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcer.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcer.java index f7398aca36964..a48161f763d96 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcer.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcer.java @@ -19,11 +19,16 @@ */ package org.neo4j.kernel.impl.enterprise; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Function; import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveIntIterator; +import org.neo4j.collection.primitive.PrimitiveIntObjectMap; import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.cursor.Cursor; import org.neo4j.kernel.api.exceptions.schema.ConstraintValidationException; @@ -31,6 +36,8 @@ import org.neo4j.kernel.api.exceptions.schema.RelationshipPropertyExistenceException; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; import org.neo4j.kernel.api.schema_new.RelationTypeSchemaDescriptor; +import org.neo4j.kernel.api.schema_new.SchemaProcessor; +import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.impl.locking.Lock; import org.neo4j.storageengine.api.NodeItem; import org.neo4j.storageengine.api.PropertyItem; @@ -42,191 +49,407 @@ import org.neo4j.storageengine.api.txstate.TxStateVisitor; import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static org.neo4j.kernel.api.exceptions.schema.ConstraintValidationException.Phase.VALIDATION; -class PropertyExistenceEnforcer extends TxStateVisitor.Delegator +class PropertyExistenceEnforcer { - private final StoreReadLayer storeLayer; - private final ReadableTransactionState txState; - private final List labelExistenceConstraints; - private final List relTypeExistenceConstraints; - - private final PrimitiveIntSet propertyKeyIds = Primitive.intSet(); - - private StorageStatement storageStatement; - - PropertyExistenceEnforcer( TxStateVisitor next, ReadableTransactionState txState, StoreReadLayer storeLayer, - List labelExistenceConstraints, - List relTypeExistenceConstraints ) + static PropertyExistenceEnforcer getOrCreatePropertyExistenceEnforcerFrom( StoreReadLayer storeLayer ) { - super( next ); - this.txState = txState; - this.storeLayer = storeLayer; - this.labelExistenceConstraints = labelExistenceConstraints; - this.relTypeExistenceConstraints = relTypeExistenceConstraints; + return storeLayer.getOrCreateSchemaDependantState( PropertyExistenceEnforcer.class, FACTORY ); } - @Override - public void visitNodePropertyChanges( long id, Iterator added, Iterator changed, - Iterator removed ) throws ConstraintValidationException + private final List nodeConstraints; + private final List relationshipConstraints; + private final PrimitiveIntObjectMap mandatoryNodePropertiesByLabel = Primitive.intObjectMap(); + private final PrimitiveIntObjectMap mandatoryRelationshipPropertiesByType = Primitive.intObjectMap(); + + private PropertyExistenceEnforcer( List nodes, List rels ) { - validateNode( id ); - super.visitNodePropertyChanges( id, added, changed, removed ); + this.nodeConstraints = nodes; + this.relationshipConstraints = rels; + for ( LabelSchemaDescriptor constraint : nodes ) + { + update( mandatoryNodePropertiesByLabel, constraint.getLabelId(), constraint.getPropertyIds() ); + } + for ( RelationTypeSchemaDescriptor constraint : rels ) + { + update( mandatoryRelationshipPropertiesByType, constraint.getRelTypeId(), constraint.getPropertyIds() ); + } } - @Override - public void visitNodeLabelChanges( long id, Set added, Set removed ) - throws ConstraintValidationException + private static void update( PrimitiveIntObjectMap map, int key, int[] values ) { - validateNode( id ); - super.visitNodeLabelChanges( id, added, removed ); + Arrays.sort( values ); + int[] current = map.get( key ); + if ( current != null ) + { + values = merge( current, values ); + } + map.put( key, values ); } - @Override - public void visitCreatedRelationship( long id, int type, long startNode, long endNode ) - throws ConstraintValidationException + TxStateVisitor decorate( TxStateVisitor visitor, ReadableTransactionState txState, StoreReadLayer storeLayer ) { - validateRelationship( id ); - super.visitCreatedRelationship( id, type, startNode, endNode ); + return new Decorator( visitor, txState, storeLayer ); } - @Override - public void visitRelPropertyChanges( long id, Iterator added, Iterator changed, - Iterator removed ) throws ConstraintValidationException + private static final PropertyExistenceEnforcer NO_CONSTRAINTS = new PropertyExistenceEnforcer( + emptyList(), emptyList() ) { - validateRelationship( id ); - super.visitRelPropertyChanges( id, added, changed, removed ); - } + @Override + TxStateVisitor decorate( TxStateVisitor visitor, ReadableTransactionState txState, StoreReadLayer storeLayer ) + { + return visitor; + } + }; + private static final Function FACTORY = storeLayer -> + { + List nodes = new ArrayList<>(); + List relationships = new ArrayList<>(); + for ( Iterator constraints = storeLayer.constraintsGetAll(); constraints.hasNext(); ) + { + ConstraintDescriptor constraint = constraints.next(); + if ( constraint.enforcesPropertyExistence() ) + { + constraint.schema().processWith( new SchemaProcessor() + { + @Override + public void processSpecific( LabelSchemaDescriptor schema ) + { + nodes.add( schema ); + } + + @Override + public void processSpecific( RelationTypeSchemaDescriptor schema ) + { + relationships.add( schema ); + } + } ); + } + } + if ( nodes.isEmpty() && relationships.isEmpty() ) + { + return NO_CONSTRAINTS; + } + return new PropertyExistenceEnforcer( nodes, relationships ); + }; - private void validateNode( long nodeId ) throws NodePropertyExistenceException + private class Decorator extends TxStateVisitor.Delegator { - if ( labelExistenceConstraints.isEmpty() ) + private final ReadableTransactionState txState; + private final StoreReadLayer storeLayer; + private final PrimitiveIntSet propertyKeyIds = Primitive.intSet(); + private StorageStatement storageStatement; + + Decorator( TxStateVisitor next, ReadableTransactionState txState, StoreReadLayer storeLayer ) + { + super( next ); + this.txState = txState; + this.storeLayer = storeLayer; + } + + @Override + public void visitNodePropertyChanges( + long id, Iterator added, Iterator changed, + Iterator removed ) throws ConstraintValidationException { - return; + validateNode( id ); + super.visitNodePropertyChanges( id, added, changed, removed ); } - try ( Cursor node = nodeCursor( nodeId ) ) + @Override + public void visitNodeLabelChanges( long id, Set added, Set removed ) + throws ConstraintValidationException { - if ( node.next() ) + validateNode( id ); + super.visitNodeLabelChanges( id, added, removed ); + } + + @Override + public void visitCreatedRelationship( long id, int type, long startNode, long endNode ) + throws ConstraintValidationException + { + validateRelationship( id ); + super.visitCreatedRelationship( id, type, startNode, endNode ); + } + + @Override + public void visitRelPropertyChanges( + long id, Iterator added, Iterator changed, + Iterator removed ) throws ConstraintValidationException + { + validateRelationship( id ); + super.visitRelPropertyChanges( id, added, changed, removed ); + } + + @Override + public void close() + { + super.close(); + if ( storageStatement != null ) { - PrimitiveIntSet labelIds = node.get().labels(); + storageStatement.close(); + } + } - propertyKeyIds.clear(); - try ( Cursor properties = properties( node.get() ) ) + private void validateNode( long nodeId ) throws NodePropertyExistenceException + { + if ( mandatoryNodePropertiesByLabel.isEmpty() ) + { + return; + } + + PrimitiveIntSet labelIds; + try ( Cursor node = node( nodeId ) ) + { + if ( node.next() ) { - while ( properties.next() ) + labelIds = node.get().labels(); + if ( labelIds.isEmpty() ) + { + return; + } + propertyKeyIds.clear(); + try ( Cursor properties = properties( node.get() ) ) { - propertyKeyIds.add( properties.get().propertyKeyId() ); + while ( properties.next() ) + { + propertyKeyIds.add( properties.get().propertyKeyId() ); + } } } + else + { + throw new IllegalStateException( format( "Node %d with changes should exist.", nodeId ) ); + } + } + + validateNodeProperties( nodeId, labelIds, propertyKeyIds ); + } + + private void validateRelationship( long id ) throws RelationshipPropertyExistenceException + { + if ( mandatoryRelationshipPropertiesByType.isEmpty() ) + { + return; + } - for ( LabelSchemaDescriptor descriptor : labelExistenceConstraints ) + int relationshipType; + int[] required; + try ( Cursor relationship = relationship( id ) ) + { + if ( relationship.next() ) { - if ( labelIds.contains( descriptor.getLabelId() ) ) + relationshipType = relationship.get().type(); + required = mandatoryRelationshipPropertiesByType.get( relationshipType ); + if ( required == null ) + { + return; + } + propertyKeyIds.clear(); + try ( Cursor properties = properties( relationship.get() ) ) { - for ( int propertyId : descriptor.getPropertyIds() ) + while ( properties.next() ) { - validateNodeProperty( nodeId, propertyId, descriptor ); + propertyKeyIds.add( properties.get().propertyKeyId() ); } } } + else + { + throw new IllegalStateException( format( "Relationship %d with changes should exist.", id ) ); + } } - else + + for ( int mandatory : required ) { - throw new IllegalStateException( format( "Node %d with changes should exist.", nodeId ) ); + if ( !propertyKeyIds.contains( mandatory ) ) + { + failRelationship( id, relationshipType, mandatory ); + } } } - } - private void validateNodeProperty( long nodeId, int propertyKey, LabelSchemaDescriptor descriptor ) - throws NodePropertyExistenceException - { - if ( !propertyKeyIds.contains( propertyKey ) ) + private Cursor node( long id ) + { + Cursor cursor = storeStatement().acquireSingleNodeCursor( id ); + return txState.augmentSingleNodeCursor( cursor, id ); + } + + private Cursor relationship( long id ) + { + Cursor cursor = storeStatement().acquireSingleRelationshipCursor( id ); + return txState.augmentSingleRelationshipCursor( cursor, id ); + } + + private Cursor properties( NodeItem node ) { - throw new NodePropertyExistenceException( descriptor, ConstraintValidationException.Phase.VALIDATION, nodeId ); + Lock lock = node.lock(); + Cursor cursor = storeStatement().acquirePropertyCursor( node.nextPropertyId(), lock ); + return txState.augmentPropertyCursor( cursor, txState.getNodeState( node.id() ) ); + } + + private Cursor properties( RelationshipItem relationship ) + { + Lock lock = relationship.lock(); + Cursor cursor = storeStatement().acquirePropertyCursor( relationship.nextPropertyId(), lock ); + return txState.augmentPropertyCursor( cursor, txState.getRelationshipState( relationship.id() ) ); + } + + private StorageStatement storeStatement() + { + return storageStatement == null ? storageStatement = storeLayer.newStatement() : storageStatement; } } - private Cursor nodeCursor( long id ) + private void validateNodeProperties( long id, PrimitiveIntSet labelIds, PrimitiveIntSet propertyKeyIds ) + throws NodePropertyExistenceException { - Cursor cursor = storeStatement().acquireSingleNodeCursor( id ); - return txState.augmentSingleNodeCursor( cursor, id ); + if ( labelIds.size() > mandatoryNodePropertiesByLabel.size() ) + { + for ( PrimitiveIntIterator labels = mandatoryNodePropertiesByLabel.iterator(); labels.hasNext(); ) + { + int label = labels.next(); + if ( labelIds.contains( label ) ) + { + validateNodeProperties( id, label, mandatoryNodePropertiesByLabel.get( label ), propertyKeyIds ); + } + } + } + else + { + for ( PrimitiveIntIterator labels = labelIds.iterator(); labels.hasNext(); ) + { + int label = labels.next(); + int[] keys = mandatoryNodePropertiesByLabel.get( label ); + if ( keys != null ) + { + validateNodeProperties( id, label, keys, propertyKeyIds ); + } + } + } } - private Cursor properties( NodeItem node ) + private void validateNodeProperties( long id, int label, int[] requiredKeys, PrimitiveIntSet propertyKeyIds ) + throws NodePropertyExistenceException { - Lock lock = node.lock(); - Cursor cursor = storeStatement().acquirePropertyCursor( node.nextPropertyId(), lock ); - return txState.augmentPropertyCursor( cursor, txState.getNodeState( node.id() ) ); + for ( int key : requiredKeys ) + { + if ( !propertyKeyIds.contains( key ) ) + { + failNode( id, label, key ); + } + } } - private StorageStatement storeStatement() + private void failNode( long id, int label, int propertyKey ) + throws NodePropertyExistenceException { - return storageStatement == null ? storageStatement = storeLayer.newStatement() : storageStatement; + for ( LabelSchemaDescriptor constraint : nodeConstraints ) + { + if ( constraint.getLabelId() == label && contains( constraint.getPropertyIds(), propertyKey ) ) + { + throw new NodePropertyExistenceException( constraint, VALIDATION, id ); + } + } + throw new IllegalStateException( format( + "Node constraint for label=%d, propertyKey=%d should exist.", + label, propertyKey ) ); } - @Override - public void close() + private void failRelationship( long id, int relationshipType, int propertyKey ) + throws RelationshipPropertyExistenceException { - super.close(); - if ( storageStatement != null ) + for ( RelationTypeSchemaDescriptor constraint : relationshipConstraints ) { - storageStatement.close(); + if ( constraint.getRelTypeId() == relationshipType && contains( constraint.getPropertyIds(), propertyKey ) ) + { + throw new RelationshipPropertyExistenceException( constraint, VALIDATION, id ); + } } + throw new IllegalStateException( format( + "Relationship constraint for relationshipType=%d, propertyKey=%d should exist.", + relationshipType, propertyKey ) ); } - private void validateRelationship( long id ) throws RelationshipPropertyExistenceException + /** + * Merges two sets of integers represented as sorted arrays. + * + * @param lhs + * a set of integers, represented as a sorted array. + * @param rhs + * a set of integers, represented as a sorted array. + * @return a set of integers, represented as a sorted array. + */ + static int[] merge( int[] lhs, int[] rhs ) { - if ( relTypeExistenceConstraints.isEmpty() ) + if ( lhs.length < rhs.length ) { - return; + return merge( rhs, lhs ); } - - try ( Cursor relationship = relationshipCursor( id ) ) + int[] merged = null; + int m = 0; + for ( int l = 0, r = 0; l < lhs.length && r < rhs.length; ) { - if ( relationship.next() ) + while ( l < lhs.length && lhs[l] < rhs[r] ) { - // Iterate all constraints and find property existence constraints that match relationship type - propertyKeyIds.clear(); - try ( Cursor properties = properties( relationship.get() ) ) + if ( merged != null ) { - while ( properties.next() ) - { - propertyKeyIds.add( properties.get().propertyKeyId() ); - } + merged[m++] = lhs[l]; } - - for ( RelationTypeSchemaDescriptor descriptor : relTypeExistenceConstraints ) + l++; + } + if ( l == lhs.length ) + { + if ( merged == null ) { - if ( relationship.get().type() == descriptor.getRelTypeId() ) - { - for ( int propertyId : descriptor.getPropertyIds() ) - { - if ( !propertyKeyIds.contains( propertyId ) ) - { - throw new RelationshipPropertyExistenceException( descriptor, - ConstraintValidationException.Phase.VALIDATION, id ); - } - } - } + merged = Arrays.copyOf( lhs, lhs.length + rhs.length - r ); + m = l; } + System.arraycopy( rhs, r, merged, m, rhs.length - r ); + m += rhs.length - r; + break; } - else + if ( lhs[l] > rhs[r] ) { - throw new IllegalStateException( format( "Relationship %d with changes should exist.", id ) ); + if ( merged == null ) + { + merged = Arrays.copyOf( lhs, lhs.length + rhs.length - r ); + m = l; + } + merged[m++] = rhs[r++]; + } + else // i.e. ( lhs[l] == rhs[r] ) + { + if ( merged != null ) + { + merged[m++] = lhs[l]; + } + l++; + r++; } } + if ( merged == null ) + { + return lhs; + } + if ( merged.length < m ) + { + merged = Arrays.copyOf( merged, m ); + } + return merged; } - private Cursor relationshipCursor( long id ) - { - Cursor cursor = storeStatement().acquireSingleRelationshipCursor( id ); - return txState.augmentSingleRelationshipCursor( cursor, id ); - } - - private Cursor properties( RelationshipItem relationship ) + private boolean contains( int[] list, int value ) { - Lock lock = relationship.lock(); - Cursor cursor = storeStatement().acquirePropertyCursor( relationship.nextPropertyId(), lock ); - return txState.augmentPropertyCursor( cursor, txState.getRelationshipState( relationship.id() ) ); + for ( int x : list ) + { + if ( value == x ) + { + return true; + } + } + return false; } } diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcerMergeTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcerMergeTest.java new file mode 100644 index 0000000000000..82a6ff76318ab --- /dev/null +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/enterprise/PropertyExistenceEnforcerMergeTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2017 "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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.enterprise; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertSame; +import static org.neo4j.kernel.impl.enterprise.PropertyExistenceEnforcer.merge; + +@RunWith( Parameterized.class ) +public class PropertyExistenceEnforcerMergeTest +{ + @Parameterized.Parameters( name = "{0}" ) + public static Iterable parameters() + { + return Arrays.asList( + lhs( 1, 2, 3 ).rhs( 1, 2, 3 ).expectLhs(), + lhs( 1, 2, 3 ).rhs( 1 ).expectLhs(), + lhs( 1, 2, 3 ).rhs( 2 ).expectLhs(), + lhs( 1, 2, 3 ).rhs( 3 ).expectLhs(), + lhs( 1 ).rhs( 1, 2, 3 ).expectRhs(), + lhs( 2 ).rhs( 1, 2, 3 ).expectRhs(), + lhs( 3 ).rhs( 1, 2, 3 ).expectRhs(), + lhs( 1, 2, 3 ).rhs( 4, 5, 6 ).expect( 1, 2, 3, 4, 5, 6 ), + lhs( 1, 3, 5 ).rhs( 2, 4, 6 ).expect( 1, 2, 3, 4, 5, 6 ), + lhs( 1, 2, 3, 5 ).rhs( 2, 4, 6 ).expect( 1, 2, 3, 4, 5, 6 ) + ); + } + + private final int[] lhs; + private final int[] rhs; + private final int[] expected; + + public PropertyExistenceEnforcerMergeTest( Input input ) + { + this.lhs = input.lhs; + this.rhs = input.rhs; + this.expected = input.expected; + } + + @Test + public void testMerge() throws Exception + { + int[] actual = merge( lhs, rhs ); + if ( lhs == expected || rhs == expected ) + { + assertSame( expected, actual ); + } + else + { + assertArrayEquals( expected, actual ); + } + } + + private static Input.Lhs lhs( int... lhs ) + { + return new Input.Lhs( lhs ); + } + + static class Input + { + final int[] lhs; + final int[] rhs; + final int[] expected; + + Input( int[] lhs, int[] rhs, int[] expected ) + { + this.lhs = lhs; + this.rhs = rhs; + this.expected = expected; + } + + @Override + public String toString() + { + return String.format( + "{lhs=%s, rhs=%s, expected=%s}", + Arrays.toString( lhs ), + Arrays.toString( rhs ), + Arrays.toString( expected ) ); + } + + static class Lhs + { + final int[] lhs; + + Lhs( int[] lhs ) + { + this.lhs = lhs; + } + + Rhs rhs( int... rhs ) + { + return new Rhs( lhs, rhs ); + } + } + + static class Rhs + { + final int[] lhs; + final int[] rhs; + + Rhs( int[] lhs, int[] rhs ) + { + + this.lhs = lhs; + this.rhs = rhs; + } + + Object[] expect( int... expected ) + { + return new Object[] {new Input( lhs, rhs, expected )}; + } + + Object[] expectLhs() + { + return new Object[] {new Input( lhs, rhs, lhs )}; + } + + Object[] expectRhs() + { + return new Object[] {new Input( lhs, rhs, rhs )}; + } + } + } +}