Skip to content

Commit

Permalink
Support NODE KEY in Cypher parser
Browse files Browse the repository at this point in the history
Initial work on NODE KEY as constraint in TxState and SchemaStore

Remove support for NODE KEY from TxState

Support NODE_KEY add/remove as syntactic sugar over unique/existence

Fixed kernel tests after NODE KEY support

First round support for NODE KEY as full-stack constraint
  • Loading branch information
craigtaverner committed Mar 21, 2017
1 parent 8cc7ea3 commit 5b2bb10
Show file tree
Hide file tree
Showing 56 changed files with 904 additions and 113 deletions.
Expand Up @@ -37,8 +37,6 @@
import org.neo4j.kernel.impl.store.record.RelationshipTypeTokenRecord;
import org.neo4j.storageengine.api.schema.SchemaRule;

import static org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor.Type.UNIQUE;

/**
* Note that this class builds up an in-memory representation of the complete schema store by being used in
* multiple phases.
Expand Down Expand Up @@ -156,7 +154,7 @@ public void checkConstraintRule( ConstraintRule rule, DynamicRecord record,
{
checkSchema( rule, record, records, engine );

if ( rule.getConstraintDescriptor().type() == UNIQUE )
if ( rule.getConstraintDescriptor().type().enforcesUniqueness() )
{
DynamicRecord previousObligation = indexObligations.put( rule.getOwnedIndex(), record.clone() );
if ( previousObligation != null )
Expand Down Expand Up @@ -201,7 +199,7 @@ public void checkIndexRule( IndexRule rule, DynamicRecord record, RecordAccess r
public void checkConstraintRule( ConstraintRule rule, DynamicRecord record,
RecordAccess records, CheckerEngine<DynamicRecord,ConsistencyReport.SchemaConsistencyReport> engine )
{
if ( rule.getConstraintDescriptor().type() == UNIQUE )
if ( rule.getConstraintDescriptor().type().enforcesUniqueness() )
{
DynamicRecord obligation = constraintObligations.get( rule.getId() );
if ( obligation == null )
Expand Down
Expand Up @@ -40,8 +40,6 @@
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.unsafe.impl.batchimport.Utils;

import static org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor.Type.EXISTS;

public class MandatoryProperties
{
private final PrimitiveIntObjectMap<int[]> nodes = Primitive.intObjectMap();
Expand All @@ -54,7 +52,7 @@ public MandatoryProperties( StoreAccess storeAccess )
SchemaStorage schemaStorage = new SchemaStorage( storeAccess.getSchemaStore() );
for ( ConstraintRule rule : constraintsIgnoringMalformed( schemaStorage ) )
{
if ( rule.getConstraintDescriptor().type() == EXISTS )
if ( rule.getConstraintDescriptor().type().enforcesPropertyExistence() )
{
rule.schema().processWith( constraintRecorder );
}
Expand Down
Expand Up @@ -290,6 +290,7 @@ object QueryTagger extends QueryTagger[String] {
lift[ASTNode] {
case x: CreateIndex => Set(CreateIndexTag)
case x: DropIndex => Set(DropIndexTag)
case x: CreateNodeKeyConstraint => Set(CreateConstraintTag)
case x: CreateUniquePropertyConstraint => Set(CreateConstraintTag)
case x: CreateNodePropertyExistenceConstraint => Set(CreateConstraintTag)
case x: CreateRelationshipPropertyExistenceConstraint => Set(CreateConstraintTag)
Expand Down
Expand Up @@ -51,6 +51,20 @@ case object ProcedureCallOrSchemaCommandPlanBuilder extends Phase[CompilerContex
Some(ProcedureCallExecutionPlan(signature, args, resolved.callResultTypes, resolved.callResultIndices,
context.notificationLogger.notifications, context.typeConverter.asPublicType))

// CREATE CONSTRAINT ON (node:Label) ASSERT (node.prop1,node.prop2) IS NODE KEY
case CreateNodeKeyConstraint(node, label, props) =>
Some(PureSideEffectExecutionPlan("CreateNodeKeyConstraint", SCHEMA_WRITE, (ctx) => {
val propertyKeyIds = props.map(p => propertyToId(ctx)(p.propertyKey))
ctx.createNodeKeyConstraint(IndexDescriptor(labelToId(ctx)(label), propertyKeyIds))
}))

// DROP CONSTRAINT ON (node:Label) ASSERT (node.prop1,node.prop2) IS NODE KEY
case DropNodeKeyConstraint(_, label, props) =>
Some(PureSideEffectExecutionPlan("DropNodeKeyConstraint", SCHEMA_WRITE, (ctx) => {
val propertyKeyIds = props.map(p => propertyToId(ctx)(p.propertyKey))
ctx.dropNodeKeyConstraint(IndexDescriptor(labelToId(ctx)(label), propertyKeyIds))
}))

// CREATE CONSTRAINT ON (node:Label) ASSERT node.prop IS UNIQUE
// CREATE CONSTRAINT ON (node:Label) ASSERT (node.prop1,node.prop2) IS UNIQUE
case CreateUniquePropertyConstraint(node, label, props) =>
Expand Down
Expand Up @@ -111,6 +111,12 @@ class DelegatingQueryContext(val inner: QueryContext) extends QueryContext {
override def getOrCreateFromSchemaState[K, V](key: K, creator: => V): V =
singleDbHit(inner.getOrCreateFromSchemaState(key, creator))

override def createNodeKeyConstraint(descriptor: IndexDescriptor): Boolean =
singleDbHit(inner.createNodeKeyConstraint(descriptor))

override def dropNodeKeyConstraint(descriptor: IndexDescriptor) =
singleDbHit(inner.dropNodeKeyConstraint(descriptor))

override def createUniqueConstraint(descriptor: IndexDescriptor): Boolean =
singleDbHit(inner.createUniqueConstraint(descriptor))

Expand Down
Expand Up @@ -101,6 +101,11 @@ trait QueryContext extends TokenContext {

def getOrCreateFromSchemaState[K, V](key: K, creator: => V): V

/* return true if the constraint was created, false if preexisting, throws if failed */
def createNodeKeyConstraint(descriptor: IndexDescriptor): Boolean

def dropNodeKeyConstraint(descriptor: IndexDescriptor)

/* return true if the constraint was created, false if preexisting, throws if failed */
def createUniqueConstraint(descriptor: IndexDescriptor): Boolean

Expand Down
Expand Up @@ -44,6 +44,8 @@ trait QueryContextAdaptation {

override def createUniqueConstraint(descriptor: IndexDescriptor): Boolean = ???

override def createNodeKeyConstraint(descriptor: IndexDescriptor): Boolean = ???

override def getOrCreateRelTypeId(relTypeName: String): Int = ???

override def getPropertiesForRelationship(relId: Long): scala.Iterator[Int] = ???
Expand Down Expand Up @@ -80,6 +82,8 @@ trait QueryContextAdaptation {

override def dropUniqueConstraint(descriptor: IndexDescriptor): Unit = ???

override def dropNodeKeyConstraint(descriptor: IndexDescriptor): Unit = ???

// Check if a runtime value is a node, relationship, path or some such value returned from
override def isGraphKernelResultValue(v: Any): Boolean = ???

Expand Down
Expand Up @@ -111,6 +111,12 @@ class ExceptionTranslatingQueryContext(val inner: QueryContext) extends QueryCon
override def getOrCreateFromSchemaState[K, V](key: K, creator: => V): V =
translateException(inner.getOrCreateFromSchemaState(key, creator))

override def createNodeKeyConstraint(descriptor: IndexDescriptor): Boolean =
translateException(inner.createNodeKeyConstraint(descriptor))

override def dropNodeKeyConstraint(descriptor: IndexDescriptor) =
translateException(inner.dropNodeKeyConstraint(descriptor))

override def createUniqueConstraint(descriptor: IndexDescriptor): Boolean =
translateException(inner.createUniqueConstraint(descriptor))

Expand Down
Expand Up @@ -481,6 +481,16 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional
override def dropIndexRule(descriptor: IndexDescriptor) =
transactionalContext.statement.schemaWriteOperations().indexDrop(descriptor)

override def createNodeKeyConstraint(descriptor: IndexDescriptor): Boolean = try {
transactionalContext.statement.schemaWriteOperations().nodeKeyConstraintCreate(descriptor)
true
} catch {
case existing: AlreadyConstrainedException => false
}

override def dropNodeKeyConstraint(descriptor: IndexDescriptor) =
transactionalContext.statement.schemaWriteOperations().constraintDrop(ConstraintDescriptorFactory.uniqueForSchema(descriptor))

override def createUniqueConstraint(descriptor: IndexDescriptor): Boolean = try {
transactionalContext.statement.schemaWriteOperations().uniquePropertyConstraintCreate(descriptor)
true
Expand Down
Expand Up @@ -89,6 +89,10 @@ trait RelationshipPropertyConstraintCommand extends PropertyConstraintCommand {
def relType: RelTypeName
}

case class CreateNodeKeyConstraint(variable: Variable, label: LabelName, properties: Seq[Property])(val position: InputPosition) extends UniquePropertyConstraintCommand

case class DropNodeKeyConstraint(variable: Variable, label: LabelName, properties: Seq[Property])(val position: InputPosition) extends UniquePropertyConstraintCommand

case class CreateUniquePropertyConstraint(variable: Variable, label: LabelName, properties: Seq[Property])(val position: InputPosition) extends UniquePropertyConstraintCommand

case class DropUniquePropertyConstraint(variable: Variable, label: LabelName, properties: Seq[Property])(val position: InputPosition) extends UniquePropertyConstraintCommand
Expand Down
Expand Up @@ -32,11 +32,13 @@ trait Command extends Parser
def Command: Rule1[ast.Command] = rule(
CreateUniqueConstraint
| CreateUniqueCompositeConstraint
| CreateNodeKeyConstraint
| CreateNodePropertyExistenceConstraint
| CreateRelationshipPropertyExistenceConstraint
| CreateIndex
| DropUniqueConstraint
| DropUniqueCompositeConstraint
| DropNodeKeyConstraint
| DropNodePropertyExistenceConstraint
| DropRelationshipPropertyExistenceConstraint
| DropIndex
Expand All @@ -63,6 +65,10 @@ trait Command extends Parser
group(keyword("CREATE") ~~ UniqueCompositeConstraintSyntax) ~~>> (ast.CreateUniquePropertyConstraint(_, _, _))
}

def CreateNodeKeyConstraint: Rule1[ast.CreateNodeKeyConstraint] = rule {
group(keyword("CREATE") ~~ NodeKeyConstraintSyntax) ~~>> (ast.CreateNodeKeyConstraint(_, _, _))
}

def CreateNodePropertyExistenceConstraint: Rule1[ast.CreateNodePropertyExistenceConstraint] = rule {
group(keyword("CREATE") ~~ NodePropertyExistenceConstraintSyntax) ~~>> (ast.CreateNodePropertyExistenceConstraint(_, _, _))
}
Expand All @@ -80,6 +86,10 @@ trait Command extends Parser
group(keyword("DROP") ~~ UniqueCompositeConstraintSyntax) ~~>> (ast.DropUniquePropertyConstraint(_, _, _))
}

def DropNodeKeyConstraint: Rule1[ast.DropNodeKeyConstraint] = rule {
group(keyword("DROP") ~~ NodeKeyConstraintSyntax) ~~>> (ast.DropNodeKeyConstraint(_, _, _))
}

def DropNodePropertyExistenceConstraint: Rule1[ast.DropNodePropertyExistenceConstraint] = rule {
group(keyword("DROP") ~~ NodePropertyExistenceConstraintSyntax) ~~>> (ast.DropNodePropertyExistenceConstraint(_, _, _))
}
Expand All @@ -94,6 +104,9 @@ trait Command extends Parser
) ~~> (_.toIndexedSeq))
}

private def NodeKeyConstraintSyntax: Rule3[Variable, LabelName, Seq[Property]] = keyword("CONSTRAINT ON") ~~ "(" ~~ Variable ~~ NodeLabel ~~ ")" ~~
keyword("ASSERT") ~~ "(" ~~ PropertyExpressions ~~ ")" ~~ keyword("IS NODE KEY")

private def UniqueConstraintSyntax: Rule3[Variable, LabelName, Property] = keyword("CONSTRAINT ON") ~~ "(" ~~ Variable ~~ NodeLabel ~~ ")" ~~
keyword("ASSERT") ~~ PropertyExpression ~~ keyword("IS UNIQUE")

Expand Down
Expand Up @@ -28,5 +28,6 @@ public enum ConstraintType
UNIQUENESS,
NODE_PROPERTY_EXISTENCE,
RELATIONSHIP_PROPERTY_EXISTENCE,
NODE_KEY
;
}
Expand Up @@ -29,6 +29,7 @@
import org.neo4j.kernel.api.schema_new.RelationTypeSchemaDescriptor;
import org.neo4j.kernel.api.schema_new.constaints.ConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.constaints.NodeExistenceConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.constaints.NodeKeyConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.constaints.RelExistenceConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.constaints.UniquenessConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.index.NewIndexDescriptor;
Expand All @@ -52,6 +53,9 @@ NewIndexDescriptor indexCreate( LabelSchemaDescriptor schemaDescriptor )
*/
void uniqueIndexDrop( NewIndexDescriptor descriptor ) throws DropIndexFailureException;

NodeKeyConstraintDescriptor nodeKeyConstraintCreate( LabelSchemaDescriptor descriptor )
throws CreateConstraintFailureException, AlreadyConstrainedException, AlreadyIndexedException;

UniquenessConstraintDescriptor uniquePropertyConstraintCreate( LabelSchemaDescriptor descriptor )
throws CreateConstraintFailureException, AlreadyConstrainedException, AlreadyIndexedException,
RepeatedPropertyInCompositeSchemaException;
Expand Down
@@ -0,0 +1,80 @@
/*
* 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.constraints;

import java.util.Arrays;

import org.neo4j.kernel.api.TokenNameLookup;
import org.neo4j.kernel.api.schema_new.constaints.NodeKeyConstraintDescriptor;
import org.neo4j.kernel.api.schema_new.LabelSchemaDescriptor;
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.NewIndexDescriptorFactory;

/**
* Description of uniqueness and existence constraint over nodes given a label and a property key id.
*
* @deprecated use {@link NodeKeyConstraintDescriptor} instead.
*/
public class NodeKeyConstraint extends NodePropertyConstraint
{
public NodeKeyConstraint( LabelSchemaDescriptor descriptor )
{
super( descriptor );
}

public NewIndexDescriptor indexDescriptor()
{
return NewIndexDescriptorFactory.forSchema( descriptor );
}

@Override
public void added( ChangeVisitor visitor )
{
visitor.visitAddedNodeKeyConstraint( this );
}

@Override
public void removed( ChangeVisitor visitor )
{
visitor.visitRemovedNodeKeyConstraint( this );
}

@Override
public String userDescription( TokenNameLookup tokenNameLookup )
{
String labelName = labelName( tokenNameLookup );
String boundIdentifier = labelName.toLowerCase();
String properties =
SchemaUtil.niceProperties( tokenNameLookup, descriptor.getPropertyIds(), boundIdentifier + "." );
if ( descriptor.getPropertyIds().length > 1 )
{
properties = "(" + properties + ")";
}
return String.format( "CONSTRAINT ON ( %s:%s ) ASSERT %s IS NODE KEY", boundIdentifier, labelName, properties );
}

@Override
public String toString()
{
return String.format( "CONSTRAINT ON ( n:label[%s] ) ASSERT n.property[%s] IS NODE KEY",
descriptor.getLabelId(), Arrays.toString( descriptor.getPropertyIds() ) );
}
}
Expand Up @@ -38,6 +38,10 @@ interface ChangeVisitor

void visitRemovedUniquePropertyConstraint( UniquenessConstraint constraint );

void visitAddedNodeKeyConstraint( NodeKeyConstraint constraint );

void visitRemovedNodeKeyConstraint( NodeKeyConstraint constraint );

void visitAddedNodePropertyExistenceConstraint( NodePropertyExistenceConstraint constraint )
throws CreateConstraintFailureException;

Expand Down
Expand Up @@ -102,4 +102,24 @@ public LabelSchemaDescriptor schema()
{
return this;
}

public int compareTo( SchemaDescriptor o )
{
if ( o instanceof LabelSchemaDescriptor )
{
LabelSchemaDescriptor other = (LabelSchemaDescriptor) o;
if ( labelId == other.getLabelId() )
{
return SchemaUtil.comparePropertyKeyIds( propertyIds, other.getPropertyIds() );
}
else
{
return labelId - other.labelId;
}
}
else
{
return -1;
}
}
}
Expand Up @@ -92,4 +92,25 @@ public int hashCode()
{
return Arrays.hashCode( propertyIds ) + 31 * relTypeId;
}

@Override
public int compareTo( SchemaDescriptor o )
{
if ( o instanceof RelationTypeSchemaDescriptor )
{
RelationTypeSchemaDescriptor other = (RelationTypeSchemaDescriptor) o;
if ( relTypeId == other.getRelTypeId() )
{
return SchemaUtil.comparePropertyKeyIds( propertyIds, other.getPropertyIds() );
}
else
{
return relTypeId - other.getRelTypeId();
}
}
else
{
return -1;
}
}
}
Expand Up @@ -33,7 +33,7 @@
* how this is done in eg. LabelSchemaDescriptor, and the SchemaProcessor and SchemaComputer interfaces need to be
* extended with methods taking the new concrete type as argument.
*/
public interface SchemaDescriptor
public interface SchemaDescriptor extends Comparable<SchemaDescriptor>
{
/**
* Computes some value by feeding this object into the given SchemaComputer.
Expand Down

0 comments on commit 5b2bb10

Please sign in to comment.