diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/exceptions/schema/NodePropertyExistenceException.java b/community/kernel/src/main/java/org/neo4j/kernel/api/exceptions/schema/NodePropertyExistenceException.java index 8bd0784a820a7..ad7831fc42dee 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/exceptions/schema/NodePropertyExistenceException.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/exceptions/schema/NodePropertyExistenceException.java @@ -21,6 +21,7 @@ import org.neo4j.kernel.api.TokenNameLookup; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; +import org.neo4j.kernel.api.schema_new.SchemaUtil; import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptorFactory; import static java.lang.String.format; @@ -42,9 +43,9 @@ public NodePropertyExistenceException( LabelSchemaDescriptor schema, @Override public String getUserMessage( TokenNameLookup tokenNameLookup ) { - return format( "Node(%d) with label `%s` must have the property `%s`", + return format( "Node(%d) with label `%s` must have the properties `%s`", nodeId, tokenNameLookup.labelGetName( schema.getLabelId() ), - tokenNameLookup.propertyKeyGetName( schema.getPropertyId() ) ); + SchemaUtil.niceProperties( tokenNameLookup, schema.getPropertyIds() ) ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/constaints/NodeExistenceConstraintDescriptor.java b/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/constaints/NodeExistenceConstraintDescriptor.java index 369452b313809..5a6462ae59d33 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/constaints/NodeExistenceConstraintDescriptor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/constaints/NodeExistenceConstraintDescriptor.java @@ -21,6 +21,7 @@ import org.neo4j.kernel.api.TokenNameLookup; import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor; +import org.neo4j.kernel.api.schema_new.SchemaUtil; public class NodeExistenceConstraintDescriptor extends ConstraintDescriptor { @@ -43,9 +44,9 @@ public String prettyPrint( TokenNameLookup tokenNameLookup ) { String labelName = escapeLabelOrRelTyp( tokenNameLookup.labelGetName( schema.getLabelId() ) ); String nodeName = labelName.toLowerCase(); - String propertyName = tokenNameLookup.propertyKeyGetName( schema.getPropertyId() ); + String properties = + SchemaUtil.niceProperties( tokenNameLookup, schema.getPropertyIds(), nodeName + ".", false ); - return String.format( "CONSTRAINT ON ( %s:%s ) ASSERT exists(%s.%s)", - nodeName, labelName, nodeName, propertyName ); + return String.format( "CONSTRAINT ON ( %s:%s ) ASSERT exists(%s)", nodeName, labelName, properties ); } } diff --git a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/CompositeNodeKeyConstraintAcceptanceTest.scala b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/CompositeNodeKeyConstraintAcceptanceTest.scala index 9572fcf06209c..84c0c17a36f73 100644 --- a/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/CompositeNodeKeyConstraintAcceptanceTest.scala +++ b/enterprise/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/CompositeNodeKeyConstraintAcceptanceTest.scala @@ -91,6 +91,30 @@ class CompositeNodeKeyConstraintAcceptanceTest extends ExecutionEngineFunSuite w } } + test("single property NODE KEY constraint should block adding nodes with missing property") { + // When + exec("CREATE CONSTRAINT ON (n:User) ASSERT (n.email) IS NODE KEY") + createLabeledNode(Map("email" -> "joe@soap.tv"), "User") + createLabeledNode(Map("email" -> "jake@soap.tv"), "User") + + // Then + a[ConstraintViolationException] should be thrownBy { + createLabeledNode(Map("firstname" -> "Joe", "lastname" -> "Soap"), "User") + } + } + + test("composite NODE KEY constraint should block adding nodes with missing properties") { + // When + exec("CREATE CONSTRAINT ON (n:User) ASSERT (n.firstname,n.lastname) IS NODE KEY") + createLabeledNode(Map("firstname" -> "Joe", "lastname" -> "Soap"), "User") + createLabeledNode(Map("firstname" -> "Joe", "lastname" -> "Smoke"), "User") + + // Then + a[ConstraintViolationException] should be thrownBy { + createLabeledNode(Map("firstname" -> "Joe", "lastnamex" -> "Soap"), "User") + } + } + test("composite NODE KEY constraint should not fail when we have nodes with different properties") { // When createLabeledNode(Map("firstname" -> "Joe", "lastname" -> "Soap"), "User") @@ -172,6 +196,16 @@ class CompositeNodeKeyConstraintAcceptanceTest extends ExecutionEngineFunSuite w ) } + test("trying to add node withoutwhen composite node key constraint exists") { + createLabeledNode(Map("name" -> "A", "surname" -> "B"), "Person") + exec("CREATE CONSTRAINT ON (person:Person) ASSERT (person.name, person.surname) IS NODE KEY") + + expectError( + "CREATE (n:Person) SET n.name = 'A', n.surname = 'B'", + String.format("Node(0) already exists with label `Person` and properties `name` = 'A', `surname` = 'B'") + ) + } + private def expectError(query: String, expectedError: String) { val error = intercept[CypherException](exec(query)) assertThat(error.getMessage, containsString(expectedError))