Skip to content

Commit

Permalink
Make IndexEntryConflictException handle composite indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Mar 1, 2017
1 parent 2a8efa4 commit fe58634
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 37 deletions.
Expand Up @@ -22,7 +22,10 @@
import java.util.Arrays; import java.util.Arrays;


import org.neo4j.helpers.Strings; import org.neo4j.helpers.Strings;
import org.neo4j.kernel.api.TokenNameLookup;
import org.neo4j.kernel.api.exceptions.KernelException; import org.neo4j.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.SchemaUtil; import org.neo4j.kernel.api.schema_new.SchemaUtil;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor;


Expand All @@ -35,18 +38,17 @@
*/ */
public class IndexEntryConflictException extends Exception public class IndexEntryConflictException extends Exception
{ {
private final Object propertyValue; private final OrderedPropertyValues propertyValues;
private final long addedNodeId; private final long addedNodeId;
private final long existingNodeId; private final long existingNodeId;


// TODO: support composite indexes public IndexEntryConflictException( long existingNodeId, long addedNodeId, OrderedPropertyValues propertyValues )
public IndexEntryConflictException( long existingNodeId, long addedNodeId, Object propertyValue )
{ {
super( format( "Both node %d and node %d share the property value %s", super( format( "Both node %d and node %d share the property value %s",
existingNodeId, addedNodeId, quote( propertyValue ) ) ); existingNodeId, addedNodeId, quote( propertyValues ) ) );
this.existingNodeId = existingNodeId; this.existingNodeId = existingNodeId;
this.addedNodeId = addedNodeId; this.addedNodeId = addedNodeId;
this.propertyValue = propertyValue; this.propertyValues = propertyValues;
} }


/** /**
Expand All @@ -61,23 +63,32 @@ public RuntimeException notAllowed( NewIndexDescriptor descriptor )
descriptor.userDescription( SchemaUtil.idTokenNameLookup ) ), this ); descriptor.userDescription( SchemaUtil.idTokenNameLookup ) ), this );
} }


public String evidenceMessage( String labelName, String propertyKey ) public String evidenceMessage( TokenNameLookup tokenNameLookup, LabelSchemaDescriptor schema )
{ {
assert schema.getPropertyIds().length == propertyValues.values().length;

String labelName = tokenNameLookup.labelGetName( schema.getLabelId() );
if ( addedNodeId == NO_SUCH_NODE ) if ( addedNodeId == NO_SUCH_NODE )
{ {
return format( "Node(%d) already exists with label `%s` and property `%s` = %s", return format( "Node(%d) already exists with label `%s` and %s",
existingNodeId, labelName, propertyKey, quote( propertyValue ) ); existingNodeId, labelName, propertyString( tokenNameLookup, schema.getPropertyIds() ) );
} }
else else
{ {
return format( "Both Node(%d) and Node(%d) have the label `%s` and property `%s` = %s", return format( "Both Node(%d) and Node(%d) have the label `%s` and %s",
existingNodeId, addedNodeId, labelName, propertyKey, quote( propertyValue ) ); existingNodeId, addedNodeId, labelName, propertyString( tokenNameLookup, schema.getPropertyIds() ) );
} }
} }


public Object getPropertyValue() public OrderedPropertyValues getPropertyValues()
{
return propertyValues;
}

public Object getSinglePropertyValue()
{ {
return propertyValue; assert propertyValues.values().length == 1;
return propertyValues.values()[0];
} }


public long getAddedNodeId() public long getAddedNodeId()
Expand Down Expand Up @@ -106,13 +117,13 @@ public boolean equals( Object o )


return addedNodeId == that.addedNodeId && return addedNodeId == that.addedNodeId &&
existingNodeId == that.existingNodeId && existingNodeId == that.existingNodeId &&
!(propertyValue != null ? !propertyValue.equals( that.propertyValue ) : that.propertyValue != null); !(propertyValues != null ? !propertyValues.equals( that.propertyValues ) : that.propertyValues != null);
} }


@Override @Override
public int hashCode() public int hashCode()
{ {
int result = propertyValue != null ? propertyValue.hashCode() : 0; int result = propertyValues != null ? propertyValues.hashCode() : 0;
result = 31 * result + (int) (addedNodeId ^ (addedNodeId >>> 32)); result = 31 * result + (int) (addedNodeId ^ (addedNodeId >>> 32));
result = 31 * result + (int) (existingNodeId ^ (existingNodeId >>> 32)); result = 31 * result + (int) (existingNodeId ^ (existingNodeId >>> 32));
return result; return result;
Expand All @@ -122,13 +133,43 @@ public int hashCode()
public String toString() public String toString()
{ {
return "IndexEntryConflictException{" + return "IndexEntryConflictException{" +
"propertyValue=" + Strings.prettyPrint( propertyValue ) + "propertyValues=" + Strings.prettyPrint( propertyValues.values() ) +
", addedNodeId=" + addedNodeId + ", addedNodeId=" + addedNodeId +
", existingNodeId=" + existingNodeId + ", existingNodeId=" + existingNodeId +
'}'; '}';
}

private String propertyString( TokenNameLookup tokenNameLookup, int[] propertyIds )
{
StringBuilder sb = new StringBuilder();
String sep = propertyIds.length > 1 ? "properties " : "property ";
for ( int i = 0; i < propertyIds.length; i++ )
{
sb.append( sep );
sep = ", ";
sb.append( '`' );
sb.append( tokenNameLookup.propertyKeyGetName( propertyIds[i] ) );
sb.append( "` = " );
sb.append( quote( propertyValues.values()[i] ) );
}
return sb.toString();
}

private static String quote( OrderedPropertyValues propertyValues )
{
StringBuilder sb = new StringBuilder();
String sep = "( ";
for ( Object value : propertyValues.values() )
{
sb.append( sep );
sep = ", ";
sb.append( quote( value ) );
}
sb.append( " )" );
return sb.toString();
} }


protected static String quote( Object propertyValue ) private static String quote( Object propertyValue )
{ {
if ( propertyValue instanceof String ) if ( propertyValue instanceof String )
{ {
Expand Down
Expand Up @@ -60,9 +60,7 @@ public String getUserMessage( TokenNameLookup tokenNameLookup )
for ( Iterator<IndexEntryConflictException> iterator = conflicts.iterator(); iterator.hasNext(); ) for ( Iterator<IndexEntryConflictException> iterator = conflicts.iterator(); iterator.hasNext(); )
{ {
IndexEntryConflictException conflict = iterator.next(); IndexEntryConflictException conflict = iterator.next();
message.append( conflict.evidenceMessage( message.append( conflict.evidenceMessage( tokenNameLookup, schema ) );
tokenNameLookup.labelGetName( schema.getLabelId() ),
tokenNameLookup.propertyKeyGetName( schema.getPropertyId() ) ) );
if ( iterator.hasNext() ) if ( iterator.hasNext() )
{ {
message.append( System.lineSeparator() ); message.append( System.lineSeparator() );
Expand Down
@@ -0,0 +1,63 @@
/*
* 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 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.kernel.api.schema_new;

import java.util.Arrays;

/**
* Holder for n property values, ordered according to a schema descriptor property id order
*/
public class OrderedPropertyValues
{
private final Object[] values;

public OrderedPropertyValues( Object... values )
{
this.values = values;
}

public Object[] values()
{
return values;
}

@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}

OrderedPropertyValues that = (OrderedPropertyValues) o;

return Arrays.deepEquals( values, that.values );
}

@Override
public int hashCode()
{
return Arrays.deepHashCode( values );
}
}
Expand Up @@ -50,6 +50,7 @@
import org.neo4j.kernel.api.schema.NodePropertyDescriptor; import org.neo4j.kernel.api.schema.NodePropertyDescriptor;
import org.neo4j.kernel.api.schema_new.IndexQuery; import org.neo4j.kernel.api.schema_new.IndexQuery;
import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.RelationTypeSchemaDescriptor; import org.neo4j.kernel.api.schema_new.RelationTypeSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory; import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory;
import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor;
Expand Down Expand Up @@ -179,7 +180,7 @@ private void validateNoExistingNodeWithLabelAndProperty(
{ {
throw new UniquePropertyValueValidationException( constraint, throw new UniquePropertyValueValidationException( constraint,
ConstraintValidationException.Phase.VALIDATION, ConstraintValidationException.Phase.VALIDATION,
new IndexEntryConflictException( existing, NO_SUCH_NODE, value ) ); new IndexEntryConflictException( existing, NO_SUCH_NODE, new OrderedPropertyValues( value ) ) );
} }
} }
catch ( IndexNotFoundKernelException | IndexBrokenKernelException | IndexNotApplicableKernelException e ) catch ( IndexNotFoundKernelException | IndexBrokenKernelException | IndexNotApplicableKernelException e )
Expand Down
@@ -0,0 +1,70 @@
/*
* 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 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.kernel.api.exceptions.index;

import org.junit.Test;

import org.neo4j.kernel.api.StatementConstants;
import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory;
import org.neo4j.kernel.api.schema_new.SchemaUtil;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class IndexEntryConflictExceptionTest
{
public static final int labelId = 1;

@Test
public void shouldMakeEntryConflicts()
{
LabelSchemaDescriptor schema = SchemaDescriptorFactory.forLabel( labelId, 2 );
OrderedPropertyValues values = new OrderedPropertyValues( "hi" );
IndexEntryConflictException e = new IndexEntryConflictException( 0L, 1L, values );

assertThat( e.evidenceMessage( SchemaUtil.idTokenNameLookup, schema ),
equalTo( "Both Node(0) and Node(1) have the label `label[1]` and property `property[2]` = 'hi'" ) );
}

@Test
public void shouldMakeEntryConflictsForOneNode()
{
LabelSchemaDescriptor schema = SchemaDescriptorFactory.forLabel( labelId, 2 );
OrderedPropertyValues values = new OrderedPropertyValues( "hi" );
IndexEntryConflictException e = new IndexEntryConflictException( 0L, StatementConstants.NO_SUCH_NODE, values );

assertThat( e.evidenceMessage( SchemaUtil.idTokenNameLookup, schema ),
equalTo( "Node(0) already exists with label `label[1]` and property `property[2]` = 'hi'" ) );
}

@Test
public void shouldMakeCompositeEntryConflicts()
{
LabelSchemaDescriptor schema = SchemaDescriptorFactory.forLabel( labelId, 2, 3, 4 );
OrderedPropertyValues values = new OrderedPropertyValues( true, "hi", new long[]{6L, 4L} );
IndexEntryConflictException e = new IndexEntryConflictException( 0L, 1L, values );

assertThat( e.evidenceMessage( SchemaUtil.idTokenNameLookup, schema ),
equalTo( "Both Node(0) and Node(1) have the label `label[1]` " +
"and properties `property[2]` = true, `property[3]` = 'hi', `property[4]` = [6, 4]" ) );
}
}
Expand Up @@ -25,7 +25,7 @@
import java.util.Arrays; import java.util.Arrays;


import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory;
import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingConfig; import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingConfig;
Expand Down Expand Up @@ -81,7 +81,7 @@ public void shouldProvidePopulatorThatEnforcesUniqueConstraints() throws Excepti
catch ( IndexEntryConflictException conflict ) catch ( IndexEntryConflictException conflict )
{ {
assertEquals( nodeId1, conflict.getExistingNodeId() ); assertEquals( nodeId1, conflict.getExistingNodeId() );
assertEquals( value, conflict.getPropertyValue() ); assertEquals( new OrderedPropertyValues( value ), conflict.getPropertyValues() );
assertEquals( nodeId2, conflict.getAddedNodeId() ); assertEquals( nodeId2, conflict.getAddedNodeId() );
} }
} }
Expand Down
Expand Up @@ -39,6 +39,7 @@
import org.neo4j.kernel.api.proc.CallableProcedure; import org.neo4j.kernel.api.proc.CallableProcedure;
import org.neo4j.kernel.api.proc.CallableUserAggregationFunction; import org.neo4j.kernel.api.proc.CallableUserAggregationFunction;
import org.neo4j.kernel.api.proc.CallableUserFunction; import org.neo4j.kernel.api.proc.CallableUserFunction;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.SchemaBoundary; import org.neo4j.kernel.api.schema_new.SchemaBoundary;
import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory; import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory;
Expand Down Expand Up @@ -118,7 +119,7 @@ public void shouldDropIndexIfPopulationFails() throws Exception
.thenReturn( 2468L ); .thenReturn( 2468L );
IndexProxy indexProxy = mock( IndexProxy.class ); IndexProxy indexProxy = mock( IndexProxy.class );
when( indexingService.getIndexProxy( 2468L ) ).thenReturn( indexProxy ); when( indexingService.getIndexProxy( 2468L ) ).thenReturn( indexProxy );
IndexEntryConflictException cause = new IndexEntryConflictException( 2, 1, "a" ); IndexEntryConflictException cause = new IndexEntryConflictException( 2, 1, new OrderedPropertyValues( "a" ) );
doThrow( new IndexPopulationFailedKernelException( SchemaBoundary.map( descriptor ), "some index", cause ) ) doThrow( new IndexPopulationFailedKernelException( SchemaBoundary.map( descriptor ), "some index", cause ) )
.when( indexProxy ).awaitStoreScanCompleted(); .when( indexProxy ).awaitStoreScanCompleted();
PropertyAccessor propertyAccessor = mock( PropertyAccessor.class ); PropertyAccessor propertyAccessor = mock( PropertyAccessor.class );
Expand Down
Expand Up @@ -30,6 +30,7 @@
import org.neo4j.kernel.api.index.IndexEntryUpdate; import org.neo4j.kernel.api.index.IndexEntryUpdate;
import org.neo4j.kernel.api.index.IndexUpdater; import org.neo4j.kernel.api.index.IndexUpdater;
import org.neo4j.kernel.api.index.PropertyAccessor; import org.neo4j.kernel.api.index.PropertyAccessor;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.impl.api.index.IndexUpdateMode; import org.neo4j.kernel.impl.api.index.IndexUpdateMode;
import org.neo4j.kernel.impl.api.index.updater.UniquePropertyIndexUpdater; import org.neo4j.kernel.impl.api.index.updater.UniquePropertyIndexUpdater;


Expand Down Expand Up @@ -118,7 +119,8 @@ public void visitEntry( Object key, Set<Long> nodeIds ) throws Exception
if ( entries.containsKey( value ) ) if ( entries.containsKey( value ) )
{ {
long existingNodeId = entries.get( value ); long existingNodeId = entries.get( value );
throw new IndexEntryConflictException( existingNodeId, nodeId, value ); throw new IndexEntryConflictException( existingNodeId, nodeId,
new OrderedPropertyValues( value ) );
} }
entries.put( value, nodeId ); entries.put( value, nodeId );
} }
Expand Down
Expand Up @@ -68,7 +68,7 @@
import org.neo4j.kernel.api.labelscan.LabelScanStore; import org.neo4j.kernel.api.labelscan.LabelScanStore;
import org.neo4j.kernel.api.labelscan.LabelScanWriter; import org.neo4j.kernel.api.labelscan.LabelScanWriter;
import org.neo4j.kernel.api.labelscan.NodeLabelUpdate; import org.neo4j.kernel.api.labelscan.NodeLabelUpdate;
import org.neo4j.kernel.api.schema.IndexDescriptor; import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory; import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptorFactory;
import org.neo4j.kernel.extension.KernelExtensionFactory; import org.neo4j.kernel.extension.KernelExtensionFactory;
Expand Down Expand Up @@ -1381,7 +1381,7 @@ public void uniquenessConstraintShouldBeCheckedOnBatchInserterShutdownAndFailIfV
catch ( RuntimeException ex ) catch ( RuntimeException ex )
{ {
// good // good
assertEquals( new IndexEntryConflictException( 0, 1, value ), ex.getCause() ); assertEquals( new IndexEntryConflictException( 0, 1, new OrderedPropertyValues( value ) ), ex.getCause() );
} }
} }


Expand Down
Expand Up @@ -33,6 +33,7 @@
import org.neo4j.kernel.api.impl.schema.LuceneDocumentStructure; import org.neo4j.kernel.api.impl.schema.LuceneDocumentStructure;
import org.neo4j.kernel.api.index.PropertyAccessor; import org.neo4j.kernel.api.index.PropertyAccessor;
import org.neo4j.kernel.api.properties.Property; import org.neo4j.kernel.api.properties.Property;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;


public class DuplicateCheckingCollector extends SimpleCollector public class DuplicateCheckingCollector extends SimpleCollector
{ {
Expand Down Expand Up @@ -93,7 +94,8 @@ private void doCollect( int doc ) throws IOException, KernelException, IndexEntr
} }
else if ( property.valueEquals( value ) ) else if ( property.valueEquals( value ) )
{ {
throw new IndexEntryConflictException( current.nodeId[i], nodeId, value ); throw new IndexEntryConflictException( current.nodeId[i], nodeId,
new OrderedPropertyValues( value ) );
} }
} }
current = current.next; current = current.next;
Expand Down

0 comments on commit fe58634

Please sign in to comment.