Skip to content

Commit

Permalink
Verify Mandatory Property Constraints on constraints creation
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusmelke committed Jul 2, 2015
1 parent 8f09061 commit 373ee8f
Show file tree
Hide file tree
Showing 15 changed files with 690 additions and 82 deletions.
Expand Up @@ -20,27 +20,41 @@
package org.neo4j.internal.cypher.acceptance

import org.neo4j.cypher.internal.compiler.v2_3.helpers.CollectionSupport
import org.neo4j.cypher.{ConstraintValidationException, ExecutionEngineFunSuite, QueryStatisticsTestSupport}
import org.neo4j.cypher.{CypherExecutionException, ConstraintValidationException, ExecutionEngineFunSuite, QueryStatisticsTestSupport}
import org.neo4j.kernel.api.exceptions.Status

class MandatoryPropertyConstraintValidationAcceptanceTest extends ExecutionEngineFunSuite with QueryStatisticsTestSupport with CollectionSupport {
class MandatoryPropertyConstraintAcceptanceTest extends ExecutionEngineFunSuite with QueryStatisticsTestSupport with CollectionSupport {

test("should enforce constraints on creation") {
test("should enforce constraints on node creation") {
// GIVEN
execute("create constraint on (node:Label1) assert node.key1 is not null")

// WHEN
val e = intercept[ConstraintValidationException](execute("create (node:Label1)"))

// THEN
e.getMessage should endWith("with label \"Label1\" does not have a \"key1\" property")
}

test("should enforce on removing property") {
// GIVEN
execute("create constraint on (node:Label1) assert node.key1 is not null")

// WHEN
execute("create (node1:Label1 {key1:'value1'})")

// THEN
intercept[ConstraintValidationException](execute("match (node:Label1) remove node.key1"))
}

test("should enforce on setting property to null") {
// GIVEN
execute("create constraint on (node:Label1) assert node.key1 is not null")

// WHEN
execute("create ( node1:Label1 {key1:'value1' } )")

//THEN
intercept[ConstraintValidationException](execute("match (node:Label1) set node.key1 = null"))
}

Expand All @@ -51,7 +65,7 @@ class MandatoryPropertyConstraintValidationAcceptanceTest extends ExecutionEngin
// WHEN
val res = execute("create (node:Label1) set node.key1 = 'foo' return node")

// THEN
//THEN
res.toList should have size 1
}

Expand All @@ -69,4 +83,15 @@ class MandatoryPropertyConstraintValidationAcceptanceTest extends ExecutionEngin
val result = execute("match (n) return count(*) as nodeCount")
result.columnAs[Int]("nodeCount").toList should equal(List(4))
}

test("should fail to create constraint when existing data violates it") {
// GIVEN
execute("create (node:Label1)")

// WHEN
val e = intercept[CypherExecutionException](execute("create constraint on (node:Label1) assert node.key1 is not null"))

//THEN
e.status should equal(Status.Schema.ConstraintCreationFailure)
}
}
Expand Up @@ -85,6 +85,12 @@ public ConstraintVerificationFailedKernelException( PropertyConstraint constrain
this.constraint = constraint;
this.evidence = null;
}
public ConstraintVerificationFailedKernelException( PropertyConstraint constraint )
{
super( Status.Schema.ConstraintVerificationFailure, "Failed to verify constraint %s", constraint);
this.constraint = constraint;
this.evidence = null;
}

public Set<Evidence> evidence()
{
Expand Down
Expand Up @@ -87,6 +87,7 @@
import static org.neo4j.collection.primitive.PrimitiveLongCollections.single;
import static org.neo4j.helpers.collection.Iterables.filter;
import static org.neo4j.helpers.collection.IteratorUtil.iterator;
import static org.neo4j.helpers.collection.IteratorUtil.loop;
import static org.neo4j.helpers.collection.IteratorUtil.resourceIterator;
import static org.neo4j.helpers.collection.IteratorUtil.singleOrNull;
import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_NODE;
Expand Down Expand Up @@ -512,8 +513,24 @@ public MandatoryPropertyConstraint mandatoryPropertyConstraintCreate( KernelStat
int propertyKeyId ) throws CreateConstraintFailureException
{
MandatoryPropertyConstraint constraint = new MandatoryPropertyConstraint( labelId, propertyKeyId );
PrimitiveLongIterator nodes = nodesGetForLabel( state, labelId );
while ( nodes.hasNext() )
{
try
{
if (!nodeGetProperty(state, nodes.next(), propertyKeyId).isDefined())
{
throw new CreateConstraintFailureException( constraint,
new ConstraintVerificationFailedKernelException( constraint ) );
}
}
catch ( EntityNotFoundException e )
{
throw new CreateConstraintFailureException( constraint, e);
}
}

state.txState().constraintDoAdd( constraint );
// TODO: validate constraint for existing data
return constraint;
}

Expand Down
Expand Up @@ -45,7 +45,7 @@ public ConstraintCreator assertPropertyIsUnique( String propertyKey )
@Override
public ConstraintCreator assertPropertyExists( String propertyKey )
{
throw new UnsupportedOperationException( "not implemented" );
return new PropertyExistsConstraintCreator( actions, label, propertyKey );
}

@Override
Expand Down
Expand Up @@ -42,10 +42,16 @@ public interface InternalSchemaActions
ConstraintDefinition createPropertyUniquenessConstraint( Label label, String propertyKey )
throws IllegalTokenNameException, TooManyLabelsException, CreateConstraintFailureException,
AlreadyConstrainedException, AlreadyIndexedException;


ConstraintDefinition createPropertyExistenceConstraint( Label label, String propertyKey )
throws IllegalTokenNameException, TooManyLabelsException, CreateConstraintFailureException,
AlreadyConstrainedException;

void dropPropertyUniquenessConstraint( Label label, String propertyKey );

void dropPropertyExistenceConstraint( Label label, String propertyKey );

String getUserMessage( KernelException e );

void assertInUnterminatedTransaction();
}
}
Expand Up @@ -45,7 +45,14 @@ public PropertyConstraintDefinition( InternalSchemaActions actions, Label label,
public void drop()
{
assertInUnterminatedTransaction();
actions.dropPropertyUniquenessConstraint( label, propertyKey );
switch(type) {
case UNIQUENESS:
actions.dropPropertyUniquenessConstraint( label, propertyKey );
break;
case MANDATORY:
actions.dropPropertyExistenceConstraint( label, propertyKey );

}
}

@Override
Expand Down
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2002-2015 "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.impl.coreapi.schema;

import org.neo4j.graphdb.ConstraintViolationException;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.schema.ConstraintCreator;
import org.neo4j.graphdb.schema.ConstraintDefinition;
import org.neo4j.kernel.api.exceptions.KernelException;

public class PropertyExistsConstraintCreator extends BaseConstraintCreator
{
// Only single property key supported a.t.m.
protected final String propertyKey;

PropertyExistsConstraintCreator( InternalSchemaActions internalCreator, Label label, String propertyKeyOrNull )
{
super( internalCreator, label );
this.propertyKey = propertyKeyOrNull;
}

@Override
public final ConstraintCreator assertPropertyIsUnique( String propertyKey )
{
throw new UnsupportedOperationException( "You are already creating a unique constraint." );
}

@Override
public ConstraintCreator assertPropertyExists( String propertyKey )
{
throw new UnsupportedOperationException( "You can only create one unique constraint at a time." );
}

@Override
public final ConstraintDefinition create()
{
assertInUnterminatedTransaction();

try
{
return actions.createPropertyExistenceConstraint( label, propertyKey );
}
catch ( KernelException e )
{
String userMessage = actions.getUserMessage( e );
throw new ConstraintViolationException( userMessage, e );
}
}
}
Expand Up @@ -40,6 +40,7 @@
import org.neo4j.kernel.api.ReadOperations;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.api.StatementTokenNameLookup;
import org.neo4j.kernel.api.constraints.MandatoryPropertyConstraint;
import org.neo4j.kernel.api.constraints.PropertyConstraint;
import org.neo4j.kernel.api.constraints.UniquenessConstraint;
import org.neo4j.kernel.api.exceptions.InvalidTransactionTypeKernelException;
Expand Down Expand Up @@ -463,6 +464,43 @@ public ConstraintDefinition createPropertyUniquenessConstraint( Label label, Str
}
}

@Override
public ConstraintDefinition createPropertyExistenceConstraint( Label label, String propertyKey )
{
try ( Statement statement = ctxSupplier.get() )
{
try
{
int labelId = statement.schemaWriteOperations().labelGetOrCreateForName( label.name() );
int propertyKeyId = statement.schemaWriteOperations().propertyKeyGetOrCreateForName( propertyKey );
statement.schemaWriteOperations().mandatoryPropertyConstraintCreate( labelId, propertyKeyId );
return new PropertyConstraintDefinition( this, label, propertyKey, ConstraintType.MANDATORY );
}
catch ( AlreadyConstrainedException e )
{
throw new ConstraintViolationException(
e.getUserMessage( new StatementTokenNameLookup( statement.readOperations() ) ), e );
}
catch ( CreateConstraintFailureException e )
{
throw new ConstraintViolationException(
e.getUserMessage( new StatementTokenNameLookup( statement.readOperations() ) ), e );
}
catch ( IllegalTokenNameException e )
{
throw new IllegalArgumentException( e );
}
catch ( TooManyLabelsException e )
{
throw new IllegalStateException( e );
}
catch ( InvalidTransactionTypeKernelException e )
{
throw new InvalidTransactionTypeException( e.getMessage(), e );
}
}
}

@Override
public void dropPropertyUniquenessConstraint( Label label, String propertyKey )
{
Expand All @@ -483,6 +521,26 @@ public void dropPropertyUniquenessConstraint( Label label, String propertyKey )
}
}

@Override
public void dropPropertyExistenceConstraint( Label label, String propertyKey )
{
try ( Statement statement = ctxSupplier.get() )
{
int labelId = statement.schemaWriteOperations().labelGetOrCreateForName( label.name() );
int propertyKeyId = statement.schemaWriteOperations().propertyKeyGetOrCreateForName( propertyKey );
PropertyConstraint constraint = new MandatoryPropertyConstraint( labelId, propertyKeyId );
statement.schemaWriteOperations().constraintDrop( constraint );
}
catch ( IllegalTokenNameException | TooManyLabelsException | DropConstraintFailureException e )
{
throw new ThisShouldNotHappenError( "Pontus", "Unable to drop property unique constraint", e );
}
catch ( InvalidTransactionTypeKernelException e )
{
throw new ConstraintViolationException( e.getMessage(), e );
}
}

@Override
public String getUserMessage( KernelException e )
{
Expand Down
Expand Up @@ -56,6 +56,7 @@
import org.neo4j.kernel.IdGeneratorFactory;
import org.neo4j.kernel.IdType;
import org.neo4j.kernel.StoreLocker;
import org.neo4j.kernel.api.constraints.MandatoryPropertyConstraint;
import org.neo4j.kernel.api.constraints.PropertyConstraint;
import org.neo4j.kernel.api.constraints.UniquenessConstraint;
import org.neo4j.kernel.api.exceptions.KernelException;
Expand Down Expand Up @@ -96,6 +97,7 @@
import org.neo4j.kernel.impl.spi.KernelContext;
import org.neo4j.kernel.impl.store.CountsComputer;
import org.neo4j.kernel.impl.store.LabelTokenStore;
import org.neo4j.kernel.impl.store.MandatoryPropertyConstraintRule;
import org.neo4j.kernel.impl.store.NeoStore;
import org.neo4j.kernel.impl.store.NodeLabels;
import org.neo4j.kernel.impl.store.NodeStore;
Expand Down Expand Up @@ -421,6 +423,9 @@ private void checkSchemaCreationConstraints( int labelId, int propertyKeyId )
case UNIQUENESS_CONSTRAINT:
otherPropertyKeyId = ((UniquePropertyConstraintRule) rule).getPropertyKey();
break;
case MANDATORY_PROPERTY_CONSTRAINT:
otherPropertyKeyId = ((MandatoryPropertyConstraintRule) rule).getPropertyKey();
break;
default:
throw new IllegalStateException( "Case not handled.");
}
Expand Down Expand Up @@ -1153,12 +1158,30 @@ public ConstraintDefinition createPropertyUniquenessConstraint( Label label, Str
return new PropertyConstraintDefinition( this, label, propertyKey, ConstraintType.UNIQUENESS );
}

@Override
public ConstraintDefinition createPropertyExistenceConstraint( Label label, String propertyKey )
{
int labelId = getOrCreateLabelId( label.name() );
int propertyKeyId = getOrCreatePropertyKeyId( propertyKey );

checkSchemaCreationConstraints( labelId, propertyKeyId );

createConstraintRule( new MandatoryPropertyConstraint( labelId, propertyKeyId ) );
return new PropertyConstraintDefinition( this, label, propertyKey, ConstraintType.MANDATORY );
}

@Override
public void dropPropertyUniquenessConstraint( Label label, String propertyKey )
{
throw unsupportedException();
}

@Override
public void dropPropertyExistenceConstraint( Label label, String propertyKey )
{
throw unsupportedException();
}

@Override
public String getUserMessage( KernelException e )
{
Expand Down

0 comments on commit 373ee8f

Please sign in to comment.