Skip to content

Commit

Permalink
Merge pull request #8906 from fickludd/3.2-composite-index-conflict-e…
Browse files Browse the repository at this point in the history
…xception

Make IndexEntryConflictException handle composite indexes
  • Loading branch information
MishaDemianenko committed Mar 2, 2017
2 parents 96e828c + b804b64 commit b0b739f
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,37 @@
*/
package org.neo4j.kernel.api.exceptions.index;

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;

import static java.lang.String.format;
import static java.lang.String.valueOf;
import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_NODE;

/**
* TODO why isn't this a {@link KernelException}?
*/
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 )
{
this( existingNodeId, addedNodeId, OrderedPropertyValues.of( 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, propertyValues ) );
this.existingNodeId = existingNodeId;
this.addedNodeId = addedNodeId;
this.propertyValue = propertyValue;
this.propertyValues = propertyValues;
}

/**
Expand All @@ -61,23 +64,31 @@ 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.size();

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 propertyValue;
return propertyValues;
}

public Object getSinglePropertyValue()
{
return propertyValues.getSinglePropertyValue();
}

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,48 +133,25 @@ public int hashCode()
public String toString()
{
return "IndexEntryConflictException{" +
"propertyValue=" + Strings.prettyPrint( propertyValue ) +
", addedNodeId=" + addedNodeId +
", existingNodeId=" + existingNodeId +
'}';
"propertyValues=" + propertyValues +
", addedNodeId=" + addedNodeId +
", existingNodeId=" + existingNodeId +
'}';
}

protected static String quote( Object propertyValue )
private String propertyString( TokenNameLookup tokenNameLookup, int[] propertyIds )
{
if ( propertyValue instanceof String )
{
return format( "'%s'", propertyValue );
}
else if ( propertyValue.getClass().isArray() )
StringBuilder sb = new StringBuilder();
String sep = propertyIds.length > 1 ? "properties " : "property ";
for ( int i = 0; i < propertyIds.length; i++ )
{
Class<?> type = propertyValue.getClass().getComponentType();
if ( type == Boolean.TYPE )
{
return Arrays.toString( (boolean[]) propertyValue );
} else if ( type == Byte.TYPE )
{
return Arrays.toString( (byte[]) propertyValue );
} else if ( type == Short.TYPE )
{
return Arrays.toString( (short[]) propertyValue );
} else if ( type == Character.TYPE )
{
return Arrays.toString( (char[]) propertyValue );
} else if ( type == Integer.TYPE )
{
return Arrays.toString( (int[]) propertyValue );
} else if ( type == Long.TYPE )
{
return Arrays.toString( (long[]) propertyValue );
} else if ( type == Float.TYPE )
{
return Arrays.toString( (float[]) propertyValue );
} else if ( type == Double.TYPE )
{
return Arrays.toString( (double[]) propertyValue );
}
return Arrays.toString( (Object[]) propertyValue );
sb.append( sep );
sep = ", ";
sb.append( '`' );
sb.append( tokenNameLookup.propertyKeyGetName( propertyIds[i] ) );
sb.append( "` = " );
sb.append( OrderedPropertyValues.quote( propertyValues.values()[i] ) );
}
return valueOf( propertyValue );
return sb.toString();
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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;

import static java.lang.String.format;
import static java.lang.String.valueOf;

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

public static OrderedPropertyValues of( Object... values )
{
return new OrderedPropertyValues( values );
}

private 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 );
}

public int size()
{
return values.length;
}

public Object getSinglePropertyValue()
{
assert values.length == 1 : "Assumed single property but had " + values.length;
return values[0];
}

@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
String sep = "( ";
for ( Object value : values )
{
sb.append( sep );
sep = ", ";
sb.append( quote( value ) );
}
sb.append( " )" );
return sb.toString();
}

public static String quote( Object propertyValue )
{
if ( propertyValue instanceof String )
{
return format( "'%s'", propertyValue );
}
else if ( propertyValue.getClass().isArray() )
{
Class<?> type = propertyValue.getClass().getComponentType();
if ( type == Boolean.TYPE )
{
return Arrays.toString( (boolean[]) propertyValue );
} else if ( type == Byte.TYPE )
{
return Arrays.toString( (byte[]) propertyValue );
} else if ( type == Short.TYPE )
{
return Arrays.toString( (short[]) propertyValue );
} else if ( type == Character.TYPE )
{
return Arrays.toString( (char[]) propertyValue );
} else if ( type == Integer.TYPE )
{
return Arrays.toString( (int[]) propertyValue );
} else if ( type == Long.TYPE )
{
return Arrays.toString( (long[]) propertyValue );
} else if ( type == Float.TYPE )
{
return Arrays.toString( (float[]) propertyValue );
} else if ( type == Double.TYPE )
{
return Arrays.toString( (double[]) propertyValue );
}
return Arrays.toString( (Object[]) propertyValue );
}
return valueOf( propertyValue );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 );
IndexEntryConflictException e = new IndexEntryConflictException( 0L, 1L, "hi" );

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 );
IndexEntryConflictException e = new IndexEntryConflictException( 0L, StatementConstants.NO_SUCH_NODE, "hi" );

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 = OrderedPropertyValues.of( 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]" ) );
}
}

0 comments on commit b0b739f

Please sign in to comment.