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 org.neo4j.helpers.Strings;
import org.neo4j.kernel.api.TokenNameLookup;
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.index.NewIndexDescriptor;

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

// TODO: support composite indexes
public IndexEntryConflictException( long existingNodeId, long addedNodeId, Object propertyValue )
public IndexEntryConflictException( long existingNodeId, long addedNodeId, OrderedPropertyValues propertyValues )
{
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.addedNodeId = addedNodeId;
this.propertyValue = propertyValue;
this.propertyValues = propertyValues;
}

/**
Expand All @@ -61,23 +63,32 @@ public RuntimeException notAllowed( NewIndexDescriptor descriptor )
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 )
{
return format( "Node(%d) already exists with label `%s` and property `%s` = %s",
existingNodeId, labelName, propertyKey, quote( propertyValue ) );
return format( "Node(%d) already exists with label `%s` and %s",
existingNodeId, labelName, propertyString( tokenNameLookup, schema.getPropertyIds() ) );
}
else
{
return format( "Both Node(%d) and Node(%d) have the label `%s` and property `%s` = %s",
existingNodeId, addedNodeId, labelName, propertyKey, quote( propertyValue ) );
return format( "Both Node(%d) and Node(%d) have the label `%s` and %s",
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()
Expand Down Expand Up @@ -106,13 +117,13 @@ public boolean equals( Object o )

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

@Override
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) (existingNodeId ^ (existingNodeId >>> 32));
return result;
Expand All @@ -122,13 +133,43 @@ public int hashCode()
public String toString()
{
return "IndexEntryConflictException{" +
"propertyValue=" + Strings.prettyPrint( propertyValue ) +
", addedNodeId=" + addedNodeId +
", existingNodeId=" + existingNodeId +
'}';
"propertyValues=" + Strings.prettyPrint( propertyValues.values() ) +
", addedNodeId=" + addedNodeId +
", 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 )
{
Expand Down
Expand Up @@ -60,9 +60,7 @@ public String getUserMessage( TokenNameLookup tokenNameLookup )
for ( Iterator<IndexEntryConflictException> iterator = conflicts.iterator(); iterator.hasNext(); )
{
IndexEntryConflictException conflict = iterator.next();
message.append( conflict.evidenceMessage(
tokenNameLookup.labelGetName( schema.getLabelId() ),
tokenNameLookup.propertyKeyGetName( schema.getPropertyId() ) ) );
message.append( conflict.evidenceMessage( tokenNameLookup, schema ) );
if ( iterator.hasNext() )
{
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_new.IndexQuery;
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.SchemaDescriptorFactory;
import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor;
Expand Down Expand Up @@ -179,7 +180,7 @@ private void validateNoExistingNodeWithLabelAndProperty(
{
throw new UniquePropertyValueValidationException( constraint,
ConstraintValidationException.Phase.VALIDATION,
new IndexEntryConflictException( existing, NO_SUCH_NODE, value ) );
new IndexEntryConflictException( existing, NO_SUCH_NODE, new OrderedPropertyValues( value ) ) );
}
}
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 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.configuration.Config;
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 )
{
assertEquals( nodeId1, conflict.getExistingNodeId() );
assertEquals( value, conflict.getPropertyValue() );
assertEquals( new OrderedPropertyValues( value ), conflict.getPropertyValues() );
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.CallableUserAggregationFunction;
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.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.SchemaDescriptorFactory;
Expand Down Expand Up @@ -118,7 +119,7 @@ public void shouldDropIndexIfPopulationFails() throws Exception
.thenReturn( 2468L );
IndexProxy indexProxy = mock( IndexProxy.class );
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 ) )
.when( indexProxy ).awaitStoreScanCompleted();
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.IndexUpdater;
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.updater.UniquePropertyIndexUpdater;

Expand Down Expand Up @@ -118,7 +119,8 @@ public void visitEntry( Object key, Set<Long> nodeIds ) throws Exception
if ( entries.containsKey( value ) )
{
long existingNodeId = entries.get( value );
throw new IndexEntryConflictException( existingNodeId, nodeId, value );
throw new IndexEntryConflictException( existingNodeId, nodeId,
new OrderedPropertyValues( value ) );
}
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.LabelScanWriter;
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.NewIndexDescriptorFactory;
import org.neo4j.kernel.extension.KernelExtensionFactory;
Expand Down Expand Up @@ -1381,7 +1381,7 @@ public void uniquenessConstraintShouldBeCheckedOnBatchInserterShutdownAndFailIfV
catch ( RuntimeException ex )
{
// 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.index.PropertyAccessor;
import org.neo4j.kernel.api.properties.Property;
import org.neo4j.kernel.api.schema_new.OrderedPropertyValues;

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 ) )
{
throw new IndexEntryConflictException( current.nodeId[i], nodeId, value );
throw new IndexEntryConflictException( current.nodeId[i], nodeId,
new OrderedPropertyValues( value ) );
}
}
current = current.next;
Expand Down

0 comments on commit fe58634

Please sign in to comment.