diff --git a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdl.scala b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdl.scala index a237aa3f9..ecf3e77aa 100644 --- a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdl.scala +++ b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdl.scala @@ -51,18 +51,22 @@ object GraphDdl { val graphTypes = ddlParts.graphTypes .keyBy(_.name) - .mapValues { graphType => tryWithGraphType(graphType.name) { - global.push(graphType.statements) - }} + .mapValues { graphType => + tryWithGraphType(graphType.name) { + global.push(graphType.statements) + } + } .view.force val graphs = ddlParts.graphs - .map { graph => tryWithGraph(graph.definition.name) { - val graphType = graph.definition.maybeGraphTypeName - .map(name => graphTypes.getOrFail(name, "Unresolved graph type")) - .getOrElse(global) - toGraph(graphType, graph) - }} + .map { graph => + tryWithGraph(graph.definition.name) { + val graphType = graph.definition.maybeGraphTypeName + .map(name => graphTypes.getOrFail(name, "Unresolved graph type")) + .getOrElse(global) + toGraph(graphType, graph) + } + } .keyBy(_.name) GraphDdl( @@ -76,10 +80,10 @@ object GraphDdl { def apply(statements: List[DdlStatement]): DdlParts = { val result = statements.foldLeft(DdlParts.empty) { - case (parts, s: SetSchemaDefinition) => parts.copy(maybeSetSchema = Some(s)) + case (parts, s: SetSchemaDefinition) => parts.copy(maybeSetSchema = Some(s)) case (parts, s: ElementTypeDefinition) => parts.copy(elementTypes = parts.elementTypes :+ s) - case (parts, s: GraphTypeDefinition) => parts.copy(graphTypes = parts.graphTypes :+ s) - case (parts, s: GraphDefinition) => parts.copy(graphs = parts.graphs :+ GraphDefinitionWithContext(s, parts.maybeSetSchema)) + case (parts, s: GraphTypeDefinition) => parts.copy(graphTypes = parts.graphTypes :+ s) + case (parts, s: GraphDefinition) => parts.copy(graphs = parts.graphs :+ GraphDefinitionWithContext(s, parts.maybeSetSchema)) } result.elementTypes.validateDistinctBy(_.name, "Duplicate element type") result.graphTypes.validateDistinctBy(_.name, "Duplicate graph type") @@ -101,8 +105,8 @@ object GraphDdl { def apply(statements: List[GraphTypeStatement]): GraphTypeParts = { val result = statements.foldLeft(GraphTypeParts.empty) { - case (parts, s: ElementTypeDefinition) => parts.copy(elementTypes = parts.elementTypes :+ s) - case (parts, s: NodeTypeDefinition) => parts.copy(nodeTypes = parts.nodeTypes :+ s) + case (parts, s: ElementTypeDefinition) => parts.copy(elementTypes = parts.elementTypes :+ s) + case (parts, s: NodeTypeDefinition) => parts.copy(nodeTypes = parts.nodeTypes :+ s) case (parts, s: RelationshipTypeDefinition) => parts.copy(relTypes = parts.relTypes :+ s) } result.elementTypes.validateDistinctBy(_.name, "Duplicate element type") @@ -124,8 +128,8 @@ object GraphDdl { def apply(mappings: List[GraphStatement]): GraphParts = mappings.foldLeft(GraphParts.empty) { - case (parts, s: GraphTypeStatement) => parts.copy(graphTypeStatements = parts.graphTypeStatements :+ s) - case (parts, s: NodeMappingDefinition) => parts.copy(nodeMappings = parts.nodeMappings :+ s) + case (parts, s: GraphTypeStatement) => parts.copy(graphTypeStatements = parts.graphTypeStatements :+ s) + case (parts, s: NodeMappingDefinition) => parts.copy(nodeMappings = parts.nodeMappings :+ s) case (parts, s: RelationshipMappingDefinition) => parts.copy(relMappings = parts.relMappings :+ s) } } @@ -168,13 +172,13 @@ object GraphDdl { .foldLeftOver(allNodeTypes) { case (schema, (labels, properties)) => schema.withNodePropertyKeys(labels, properties) } - .foldLeftOver(allNodeTypes.keySet.flatten.map(resolveElementType)) { case (schema, eType) => + .foldLeftOver(allNodeTypes.keySet.flatten.flatMap(resolveElementTypes)) { case (schema, eType) => eType.maybeKey.fold(schema)(key => schema.withNodeKey(eType.name, key._2)) } .foldLeftOver(allEdgeTypes) { case (schema, (label, properties)) => schema.withRelationshipPropertyKeys(label, properties) } - .foldLeftOver(allEdgeTypes.keySet.map(resolveElementType)) { case (schema, eType) => + .foldLeftOver(allEdgeTypes.keySet.flatMap(resolveElementTypes)) { case (schema, eType) => eType.maybeKey.fold(schema)(key => schema.withNodeKey(eType.name, key._2)) } .withSchemaPatterns(allPatterns.toSeq: _*) @@ -191,40 +195,83 @@ object GraphDdl { elementTypes = local.elementTypes, nodeTypes = Set( - parts.nodeTypes.map(_.elementTypes), - parts.relTypes.map(_.sourceNodeType.elementTypes), - parts.relTypes.map(_.targetNodeType.elementTypes) + parts.nodeTypes.map(local.resolveNodeLabels), + parts.relTypes.map(relType => local.resolveNodeLabels(relType.sourceNodeType)), + parts.relTypes.map(relType => local.resolveNodeLabels(relType.targetNodeType)) ).flatten.map(labels => labels -> tryWithNode(labels)( - mergeProperties(labels.map(local.resolveElementType)) + mergeProperties(labels.flatMap(local.resolveElementTypes)) )).toMap, edgeTypes = Set( - parts.relTypes.map(_.elementType) + parts.relTypes.map(local.resolveRelType) ).flatten.map(label => label -> tryWithRel(label)( - mergeProperties(Set(local.resolveElementType(label))) + mergeProperties(local.resolveElementTypes(label)) )).toMap, patterns = parts.relTypes.map(relType => SchemaPattern( - relType.sourceNodeType.elementTypes, - relType.elementType, - relType.targetNodeType.elementTypes + local.resolveNodeLabels(relType.sourceNodeType), + local.resolveRelType(relType), + local.resolveNodeLabels(relType.targetNodeType) )).toSet ) } + def toNodeType(nodeTypeDefinition: NodeTypeDefinition): NodeType = + NodeType(resolveNodeLabels(nodeTypeDefinition)) + + def toRelType(relationshipTypeDefinition: RelationshipTypeDefinition): RelationshipType = + RelationshipType( + startNodeType = toNodeType(relationshipTypeDefinition.sourceNodeType), + elementType = resolveRelType(relationshipTypeDefinition), + endNodeType = toNodeType(relationshipTypeDefinition.targetNodeType)) + + private def resolveNodeLabels(nodeType: NodeTypeDefinition): Set[String] = + tryWithNode(nodeType.elementTypes)(nodeType.elementTypes.flatMap(resolveElementTypes).map(_.name)) + + private def resolveRelType(relType: RelationshipTypeDefinition): String = { + val resolved = tryWithRel(relType.elementType)(resolveElementTypes(relType.elementType)) + + if (resolved.size > 1) { + illegalInheritance("Inheritance not allowed for relationship types ", relType.elementType) + } + resolved.head.name + } + private def mergeProperties(elementTypes: Set[ElementTypeDefinition]): PropertyKeys = { elementTypes .flatMap(_.properties) .foldLeft(PropertyKeys.empty) { case (props, (name, cypherType)) => props.get(name).filter(_ != cypherType) match { case Some(t) => incompatibleTypes(name, cypherType, t) - case None => props.updated(name, cypherType) + case None => props.updated(name, cypherType) } } } + private def resolveElementTypes(name: String): Set[ElementTypeDefinition] = { + val elementType = resolveElementType(name) + detectCircularDependency(elementType) + resolveParents(elementType) + } + private def resolveElementType(name: String): ElementTypeDefinition = allElementTypes.getOrElse(name, unresolved(s"Unresolved element type", name)) + + private def resolveParents(node: ElementTypeDefinition): Set[ElementTypeDefinition] = + node.parents.map(resolveElementType).flatMap(resolveParents) + node + + private def detectCircularDependency(node: ElementTypeDefinition): Unit = { + def traverse(node: ElementTypeDefinition, path: List[ElementTypeDefinition]): Unit = { + node.parents.foreach { p => + val parentElementType = allElementTypes.getOrElse(p, unresolved(s"Unresolved element type", p)) + if (path.contains(parentElementType)) { + illegalInheritance("Circular dependency detected", (path.map(_.name) :+ p).mkString(" -> ")) + } + traverse(parentElementType, path :+ parentElementType) + } + } + traverse(node, List(node)) + } } private def toGraph( @@ -236,28 +283,31 @@ object GraphDdl { .push(parts.graphTypeStatements) .push(parts.nodeMappings.map(_.nodeType) ++ parts.relMappings.map(_.relType)) + val okapiSchema = graphType.asOkapiSchema + Graph( name = GraphName(graph.definition.name), - graphType = graphType.asOkapiSchema, + graphType = okapiSchema, nodeToViewMappings = parts.nodeMappings - .flatMap(nm => toNodeToViewMappings(graphType.asOkapiSchema, graph.maybeSetSchema, nm)) + .flatMap(nmd => toNodeToViewMappings(graphType.toNodeType(nmd.nodeType), okapiSchema, graph.maybeSetSchema, nmd)) .validateDistinctBy(_.key, "Duplicate node mapping") .keyBy(_.key), edgeToViewMappings = parts.relMappings - .flatMap(em => toEdgeToViewMappings(graphType.asOkapiSchema, graph.maybeSetSchema, em)) + .flatMap(rmd => toEdgeToViewMappings(graphType.toRelType(rmd.relType), okapiSchema, graph.maybeSetSchema, rmd)) .validateDistinctBy(_.key, "Duplicate relationship mapping") ) } private def toNodeToViewMappings( - graphType: Schema, + nodeType: NodeType, + okapiSchema: Schema, maybeSetSchema: Option[SetSchemaDefinition], nmd: NodeMappingDefinition ): Seq[NodeToViewMapping] = { nmd.nodeToView.map { nvd => tryWithContext(s"Error in node mapping for: ${nmd.nodeType.elementTypes.mkString(",")}") { - val nodeKey = NodeViewKey(toNodeType(nmd.nodeType), toViewId(maybeSetSchema, nvd.viewId)) + val nodeKey = NodeViewKey(nodeType, toViewId(maybeSetSchema, nvd.viewId)) tryWithContext(s"Error in node mapping for: $nodeKey") { NodeToViewMapping( @@ -265,7 +315,7 @@ object GraphDdl { view = toViewId(maybeSetSchema, nvd.viewId), propertyMappings = toPropertyMappings( elementTypes = nodeKey.nodeType.elementTypes, - graphTypePropertyKeys = graphType.nodePropertyKeys(nodeKey.nodeType.elementTypes).keySet, + graphTypePropertyKeys = okapiSchema.nodePropertyKeys(nodeKey.nodeType.elementTypes).keySet, maybePropertyMapping = nvd.maybePropertyMapping ) ) @@ -275,14 +325,15 @@ object GraphDdl { } private def toEdgeToViewMappings( - graphType: Schema, + relType: RelationshipType, + okapiSchema: Schema, maybeSetSchema: Option[SetSchemaDefinition], rmd: RelationshipMappingDefinition ): Seq[EdgeToViewMapping] = { rmd.relTypeToView.map { rvd => tryWithContext(s"Error in relationship mapping for: ${rmd.relType}") { - val edgeKey = EdgeViewKey(toRelType(rmd.relType), toViewId(maybeSetSchema, rvd.viewDef.viewId)) + val edgeKey = EdgeViewKey(relType, toViewId(maybeSetSchema, rvd.viewDef.viewId)) tryWithContext(s"Error in relationship mapping for: $edgeKey") { EdgeToViewMapping( @@ -310,7 +361,7 @@ object GraphDdl { ), propertyMappings = toPropertyMappings( elementTypes = Set(rmd.relType.elementType), - graphTypePropertyKeys = graphType.relationshipPropertyKeys(rmd.relType.elementType).keySet, + graphTypePropertyKeys = okapiSchema.relationshipPropertyKeys(rmd.relType.elementType).keySet, maybePropertyMapping = rvd.maybePropertyMapping ) ) @@ -339,15 +390,6 @@ object GraphDdl { } } - private def toNodeType(nodeTypeDefinition: NodeTypeDefinition): NodeType = - NodeType(nodeTypeDefinition.elementTypes) - - private def toRelType(relTypeDefinition: RelationshipTypeDefinition): RelationshipType = - RelationshipType( - startNodeType = toNodeType(relTypeDefinition.sourceNodeType), - elementType = relTypeDefinition.elementType, - endNodeType = toNodeType(relTypeDefinition.targetNodeType)) - private def toPropertyMappings( elementTypes: Set[String], graphTypePropertyKeys: Set[String], @@ -384,7 +426,7 @@ object GraphDdl { def validateDistinctBy[K](key: T => K, msg: String): C[T] = { elems.groupBy(key).foreach { case (k, values) if values.size > 1 => duplicate(msg, k) - case _ => + case _ => } elems } @@ -467,7 +509,7 @@ case class Join( ) object NodeType { - def apply(elementTypes: String*): NodeType = NodeType(elementTypes.toSet) + def apply(elementTypeLabels: String*): NodeType = NodeType(elementTypeLabels.toSet) } case class NodeType(elementTypes: Set[String]) { @@ -481,11 +523,11 @@ object RelationshipType { case class RelationshipType(startNodeType: NodeType, elementType: String, endNodeType: NodeType) { override def toString: String = s"$startNodeType-[$elementType]->$endNodeType" - } trait ElementViewKey { def elementType: Set[String] + def viewId: ViewId } diff --git a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlAst.scala b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlAst.scala index 1f76c9c6d..7c1fd900a 100644 --- a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlAst.scala +++ b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlAst.scala @@ -57,6 +57,7 @@ case class SetSchemaDefinition( case class ElementTypeDefinition( name: String, + parents: Set[String] = Set.empty, properties: Map[String, CypherType] = Map.empty, maybeKey: Option[KeyDefinition] = None ) extends GraphDdlAst with DdlStatement with GraphTypeStatement diff --git a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlException.scala b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlException.scala index 7238e3477..2e33cff90 100644 --- a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlException.scala +++ b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlException.scala @@ -36,7 +36,7 @@ abstract class GraphDdlException(msg: String, cause: Option[Exception] = None) e private[graphddl] object GraphDdlException { def unresolved(desc: String, reference: Any): Nothing = throw UnresolvedReferenceException( - s"""$desc: $reference""" + s"$desc: $reference" ) def unresolved(desc: String, reference: Any, available: Traversable[Any]): Nothing = throw UnresolvedReferenceException( @@ -45,7 +45,11 @@ private[graphddl] object GraphDdlException { ) def duplicate(desc: String, definition: Any): Nothing = throw DuplicateDefinitionException( - s"""$desc: $definition""" + s"$desc: $definition" + ) + + def illegalInheritance(desc: String, reference: Any): Nothing = throw IllegalInheritanceException( + s"$desc: $reference" ) def incompatibleTypes(msg: String): Nothing = @@ -73,6 +77,8 @@ case class UnresolvedReferenceException(msg: String, cause: Option[Exception] = case class DuplicateDefinitionException(msg: String, cause: Option[Exception] = None) extends GraphDdlException(msg, cause) +case class IllegalInheritanceException(msg: String, cause: Option[Exception] = None) extends GraphDdlException(msg, cause) + case class TypeException(msg: String, cause: Option[Exception] = None) extends GraphDdlException(msg, cause) case class MalformedIdentifier(msg: String, cause: Option[Exception] = None) extends GraphDdlException(msg, cause) diff --git a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlParser.scala b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlParser.scala index fd753d6d1..2af7dd319 100644 --- a/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlParser.scala +++ b/graph-ddl/src/main/scala/org/opencypher/graphddl/GraphDdlParser.scala @@ -65,6 +65,7 @@ object GraphDdlParser { private def CREATE[_: P]: P[Unit] = keyword("CREATE") private def ELEMENT[_: P]: P[Unit] = keyword("ELEMENT") + private def EXTENDS[_: P]: P[Unit] = keyword("EXTENDS") private def KEY[_: P]: P[Unit] = keyword("KEY") private def GRAPH[_: P]: P[Unit] = keyword("GRAPH") private def TYPE[_: P]: P[Unit] = keyword("TYPE") @@ -87,22 +88,23 @@ object GraphDdlParser { P(identifier.! ~/ CypherTypeParser.cypherType) private def properties[_: P]: P[Map[String, CypherType]] = - P("(" ~/ property.rep(min = 1, sep = ",").map(_.toMap) ~/ ")") + P("(" ~/ property.rep(min = 0, sep = ",").map(_.toMap) ~/ ")") private def keyDefinition[_: P]: P[(String, Set[String])] = P(KEY ~/ identifier.! ~/ "(" ~/ identifier.!.rep(min = 1, sep = ",").map(_.toSet) ~/ ")") - def elementTypeDefinition[_: P]: P[ElementTypeDefinition] = { - P(identifier.! ~/ properties.? ~/ keyDefinition.?).map { - case (id, None, maybeKey) => ElementTypeDefinition(id, maybeKey = maybeKey) - case (id, Some(props), maybeKey) => ElementTypeDefinition(id, props, maybeKey) + private def extendsDefinition[_: P]: P[Set[String]] = + P(EXTENDS ~/ identifier.!.rep(min = 1, sep = ",").map(_.toSet)) + + def elementTypeDefinition[_: P]: P[ElementTypeDefinition] = + P(identifier.! ~/ extendsDefinition.? ~/ properties.? ~/ keyDefinition.?).map { + case (id, maybeParents, maybeProps, maybeKey) => + ElementTypeDefinition(id, maybeParents.getOrElse(Set.empty), maybeProps.getOrElse(Map.empty), maybeKey) } - } def globalElementTypeDefinition[_: P]: P[ElementTypeDefinition] = P(CREATE ~ ELEMENT ~/ TYPE ~/ elementTypeDefinition) - // ==== Schema ==== def elementType[_: P]: P[String] = diff --git a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlAcceptanceTest.scala b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlAcceptanceTest.scala index b5e673d8a..0cadd5559 100644 --- a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlAcceptanceTest.scala +++ b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlAcceptanceTest.scala @@ -29,7 +29,7 @@ package org.opencypher.graphddl import org.opencypher.graphddl.GraphDdlParser._ import org.opencypher.okapi.api.graph.GraphName import org.opencypher.okapi.api.schema.{Schema, SchemaPattern} -import org.opencypher.okapi.api.types.{CTBoolean, CTInteger, CTString} +import org.opencypher.okapi.api.types.{CTBoolean, CTFloat, CTInteger, CTString} import org.opencypher.okapi.testing.BaseTestSuite import org.opencypher.okapi.testing.MatchHelper.equalWithTracing @@ -225,6 +225,57 @@ class GraphDdlAcceptanceTest extends BaseTestSuite { ) } + it("merges property keys for label combination based on element type hierarchy") { + // Given + val ddl = + s"""|CREATE ELEMENT TYPE A ( foo STRING ) + |CREATE ELEMENT TYPE B EXTENDS A ( bar STRING ) + | + |CREATE GRAPH TYPE $typeName ( + | (A), + | (B) + |) + |CREATE GRAPH $graphName OF $typeName () + |""".stripMargin + + GraphDdl(ddl).graphs(graphName).graphType should equal( + Schema.empty + .withNodePropertyKeys("A")("foo" -> CTString) + .withNodePropertyKeys("A", "B")("foo" -> CTString, "bar" -> CTString) + ) + } + + it("merges property keys for label combination based on element type with multi-inheritance") { + // Given + val ddl = + s"""|CREATE ELEMENT TYPE A ( a STRING ) + |CREATE ELEMENT TYPE B EXTENDS A ( b STRING ) + |CREATE ELEMENT TYPE C EXTENDS A ( c STRING ) + | + |CREATE GRAPH TYPE $typeName ( + | D EXTENDS B, C ( d INTEGER ), + | E ( e FLOAT ), + | (A), + | (B), + | (C), + | (D), + | (A, E), + | (D, E) + |) + |CREATE GRAPH $graphName OF $typeName () + |""".stripMargin + + GraphDdl(ddl).graphs(graphName).graphType should equal( + Schema.empty + .withNodePropertyKeys("A")("a" -> CTString) + .withNodePropertyKeys("A", "B")("a" -> CTString, "b" -> CTString) + .withNodePropertyKeys("A", "C")("a" -> CTString, "c" -> CTString) + .withNodePropertyKeys("A", "B", "C", "D")("a" -> CTString, "b" -> CTString, "c" -> CTString, "d" -> CTInteger) + .withNodePropertyKeys("A", "E")("a" -> CTString, "e" -> CTFloat) + .withNodePropertyKeys("A", "B", "C", "D", "E")("a" -> CTString, "b" -> CTString, "c" -> CTString, "d" -> CTInteger, "e" -> CTFloat) + ) + } + it("merges identical property keys with same type") { // Given val ddl = @@ -284,10 +335,10 @@ class GraphDdlAcceptanceTest extends BaseTestSuite { ddlDefinition should equalWithTracing( DdlDefinition(List( SetSchemaDefinition("foo", "bar"), - ElementTypeDefinition("A", Map("name" -> CTString)), - ElementTypeDefinition("B", Map("sequence" -> CTInteger, "nationality" -> CTString.nullable, "age" -> CTInteger.nullable)), + ElementTypeDefinition("A", properties = Map("name" -> CTString)), + ElementTypeDefinition("B", properties = Map("sequence" -> CTInteger, "nationality" -> CTString.nullable, "age" -> CTInteger.nullable)), ElementTypeDefinition("TYPE_1"), - ElementTypeDefinition("TYPE_2", Map("prop" -> CTBoolean.nullable)), + ElementTypeDefinition("TYPE_2", properties = Map("prop" -> CTBoolean.nullable)), GraphTypeDefinition( name = typeName, statements = List( diff --git a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlParserTest.scala b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlParserTest.scala index d0575a0b4..74c528d4d 100644 --- a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlParserTest.scala +++ b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlParserTest.scala @@ -97,21 +97,21 @@ class GraphDdlParserTest extends BaseTestSuite with MockitoSugar with TestNameFi } } - describe("label definitions") { + describe("element type definitions") { it("parses A") { success(elementTypeDefinition(_), ElementTypeDefinition("A")) } it("parses A ( foo string? )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("foo" -> CTString.nullable))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("foo" -> CTString.nullable))) } it("parses A ( key FLOAT )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTFloat))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTFloat))) } it("parses A ( key FLOAT? )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTFloat.nullable))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTFloat.nullable))) } it("!parses A ( key _ STRING )") { @@ -119,45 +119,57 @@ class GraphDdlParserTest extends BaseTestSuite with MockitoSugar with TestNameFi } it("parses A ( key1 FLOAT, key2 STRING)") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key1" -> CTFloat, "key2" -> CTString))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key1" -> CTFloat, "key2" -> CTString))) } it("parses A ( key DATE )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTDate))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTDate))) } it("parses A ( key DATE? )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTDateOrNull))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTDateOrNull))) } it("parses A ( key LOCALDATETIME )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTLocalDateTime))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTLocalDateTime))) } it("parses A ( key LOCALDATETIME? )") { - success(elementTypeDefinition(_), ElementTypeDefinition("A", Map("key" -> CTLocalDateTimeOrNull))) + success(elementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("key" -> CTLocalDateTimeOrNull))) } - it("!parses A ()") { - failure(elementTypeDefinition(_)) + it("parses A ()") { + success(elementTypeDefinition(_), ElementTypeDefinition("A")) + } + + it("parses A EXTENDS B ()") { + success(elementTypeDefinition(_), ElementTypeDefinition("A", parents = Set("B"))) + } + + it("parses A EXTENDS B, C ()") { + success(elementTypeDefinition(_), ElementTypeDefinition("A", parents = Set("B", "C"))) + } + + it("parses A EXTENDS B, C ( key STRING )") { + success(elementTypeDefinition(_), ElementTypeDefinition("A", parents = Set("B", "C"), properties = Map("key" -> CTString))) } } - describe("catalog label definition") { + describe("catalog element type definition") { it("parses CREATE ELEMENT TYPE A") { success(globalElementTypeDefinition(_), ElementTypeDefinition("A")) } it("parses CREATE ELEMENT TYPE A ( foo STRING ) ") { - success(globalElementTypeDefinition(_), ElementTypeDefinition("A", Map("foo" -> CTString))) + success(globalElementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("foo" -> CTString))) } it("parses CREATE ELEMENT TYPE A KEY A_NK (foo, bar)") { - success(globalElementTypeDefinition(_), ElementTypeDefinition("A", Map.empty, Some("A_NK" -> Set("foo", "bar")))) + success(globalElementTypeDefinition(_), ElementTypeDefinition("A", properties = Map.empty, maybeKey = Some("A_NK" -> Set("foo", "bar")))) } it("parses CREATE ELEMENT TYPE A ( foo STRING ) KEY A_NK (foo, bar)") { - success(globalElementTypeDefinition(_), ElementTypeDefinition("A", Map("foo" -> CTString), Some("A_NK" -> Set("foo", "bar")))) + success(globalElementTypeDefinition(_), ElementTypeDefinition("A", properties = Map("foo" -> CTString), maybeKey = Some("A_NK" -> Set("foo", "bar")))) } it("!parses CREATE ELEMENT TYPE A ( foo STRING ) KEY A ()") { @@ -240,10 +252,10 @@ class GraphDdlParserTest extends BaseTestSuite with MockitoSugar with TestNameFi |CREATE ELEMENT TYPE TYPE_2 ( prop BOOLEAN? ) """.stripMargin) shouldEqual DdlDefinition(List( SetSchemaDefinition("foo", "bar"), - ElementTypeDefinition("A", Map("name" -> CTString)), - ElementTypeDefinition("B", Map("sequence" -> CTInteger, "nationality" -> CTString.nullable, "age" -> CTInteger.nullable)), + ElementTypeDefinition("A", properties = Map("name" -> CTString)), + ElementTypeDefinition("B", properties = Map("sequence" -> CTInteger, "nationality" -> CTString.nullable, "age" -> CTInteger.nullable)), ElementTypeDefinition("TYPE_1"), - ElementTypeDefinition("TYPE_2", Map("prop" -> CTBoolean.nullable)) + ElementTypeDefinition("TYPE_2", properties = Map("prop" -> CTBoolean.nullable)) )) } diff --git a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlTest.scala b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlTest.scala index dbf293a57..894f62193 100644 --- a/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlTest.scala +++ b/graph-ddl/src/test/scala/org/opencypher/graphddl/GraphDdlTest.scala @@ -72,9 +72,9 @@ class GraphDdlTest extends FunSpec with Matchers { val personKey1 = NodeViewKey(NodeType("Person"), ViewId(maybeSetSchema, List("personView1"))) val personKey2 = NodeViewKey(NodeType("Person"), ViewId(maybeSetSchema, List("personView2"))) - val bookKey = NodeViewKey(NodeType("Book"), ViewId(maybeSetSchema, List("bookView"))) - val readsKey1 = EdgeViewKey(RelationshipType("Person", "READS", "Book"), ViewId(maybeSetSchema, List("readsView1"))) - val readsKey2 = EdgeViewKey(RelationshipType("Person", "READS", "Book"), ViewId(maybeSetSchema, List("readsView2"))) + val bookKey = NodeViewKey(NodeType("Book"), ViewId(maybeSetSchema, List("bookView"))) + val readsKey1 = EdgeViewKey(RelationshipType("Person", "READS", "Book"), ViewId(maybeSetSchema, List("readsView1"))) + val readsKey2 = EdgeViewKey(RelationshipType("Person", "READS", "Book"), ViewId(maybeSetSchema, List("readsView2"))) val expected = GraphDdl( Map( @@ -200,79 +200,171 @@ class GraphDdlTest extends FunSpec with Matchers { ) } + describe("validate EXTENDS syntax for mappings") { + + val A_a = NodeViewKey(NodeType("A"), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("a"))) + val A_ab = NodeViewKey(NodeType("A", "B"), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("a_b"))) + + val expectedGraph = Graph( + name = GraphName("myGraph"), + graphType = Schema.empty + .withNodePropertyKeys("A")("x" -> CTString) + .withNodePropertyKeys("A", "B")("x" -> CTString, "y" -> CTString) + .withRelationshipPropertyKeys("R")("y" -> CTString) + .withSchemaPatterns(SchemaPattern("A", "R", "A")) + .withSchemaPatterns(SchemaPattern(Set("A", "B"), "R", Set("A"))), + nodeToViewMappings = Map( + A_a -> NodeToViewMapping(NodeType("A"), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("a")), Map("x" -> "x")), + A_ab -> NodeToViewMapping(NodeType("A", "B"), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("a_b")), Map("x" -> "x", "y" -> "y")) + ), + edgeToViewMappings = List( + EdgeToViewMapping(RelationshipType("A", "R", "A"), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("r")), + StartNode(A_a, List(Join("id", "id"))), + EndNode(A_a, List(Join("id", "id"))), + Map("y" -> "y") + ), + EdgeToViewMapping(RelationshipType(NodeType("A", "B"), "R", NodeType("A")), ViewId(Some(SetSchemaDefinition("ds1", "db1")), List("r")), + StartNode(A_ab, List(Join("id", "id"))), + EndNode(A_a, List(Join("id", "id"))), + Map("y" -> "y") + ) + ) + ) + + it("allows compact inline graph definition with complex node type") { + val ddl = GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH myGraph ( + | A (x STRING), + | B (y STRING), + | R (y STRING), + | + | (A)-[R]->(A), + | (A, B)-[R]->(A), + | + | (A) FROM a, + | (A, B) FROM a_b, + | + | (A)-[R]->(A) FROM r e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id, + | (A, B)-[R]->(A) FROM r e + | START NODES (A, B) FROM a_b n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin) + + ddl.graphs(GraphName("myGraph")) shouldEqual expectedGraph + } + + it("allows compact inline graph definition with complex node type based on inheritance") { + val ddl = GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH myGraph ( + | A (x STRING), + | B EXTENDS A (y STRING), + | R (y STRING), + | + | (A)-[R]->(A), + | (B)-[R]->(A), + | + | (A) FROM a, + | (B) FROM a_b, + | + | (A)-[R]->(A) FROM r e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id, + | (B)-[R]->(A) FROM r e + | START NODES (B) FROM a_b n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin) + + ddl.graphs(GraphName("myGraph")) shouldEqual expectedGraph + } + + } + + it("allows these equivalent graph definitions") { val ddls = List( // most compact form - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH myGraph ( - | A (x STRING), B (y STRING), - | (A) FROM a, - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin), + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH myGraph ( + | A (x STRING), B (y STRING), + | (A) FROM a, + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin), // mixed order - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH myGraph ( - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id, - | A (x STRING), B (y STRING), - | (A) FROM a - |) - """.stripMargin), + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH myGraph ( + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id, + | A (x STRING), B (y STRING), + | (A) FROM a + |) + """.stripMargin), // explicit node and rel type definition - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH myGraph ( - | A (x STRING), B (y STRING), - | (A), (A)-[B]->(A), - | (A) FROM a, - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin), + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH myGraph ( + | A (x STRING), B (y STRING), + | (A), (A)-[B]->(A), + | (A) FROM a, + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin), // pure type definitions extracted to graph type - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH TYPE myType ( - | A (x STRING), B (y STRING), - | (A), (A)-[B]->(A) - |) - |CREATE GRAPH myGraph OF myType ( - | (A) FROM a, - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin), + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH TYPE myType ( + | A (x STRING), B (y STRING), + | (A), (A)-[B]->(A) + |) + |CREATE GRAPH myGraph OF myType ( + | (A) FROM a, + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin), // shadowing - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH TYPE myType ( - | A (x STRING), B (foo STRING), - | (A), (A)-[B]->(A) - |) - |CREATE GRAPH myGraph OF myType ( - | B (y STRING), - | (A) FROM a, - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin), + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH TYPE myType ( + | A (x STRING), B (foo STRING), + | (A), (A)-[B]->(A) + |) + |CREATE GRAPH myGraph OF myType ( + | B (y STRING), + | (A) FROM a, + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin), // only label types in graph type - GraphDdl("""SET SCHEMA ds1.db1 - |CREATE GRAPH TYPE myType ( - | A (x STRING), B (foo STRING) - |) - |CREATE GRAPH myGraph OF myType ( - | B (y STRING), - | (A) FROM a, - | (A)-[B]->(A) FROM b e - | START NODES (A) FROM a n JOIN ON e.id = n.id - | END NODES (A) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin) + GraphDdl( + """SET SCHEMA ds1.db1 + |CREATE GRAPH TYPE myType ( + | A (x STRING), B (foo STRING) + |) + |CREATE GRAPH myGraph OF myType ( + | B (y STRING), + | (A) FROM a, + | (A)-[B]->(A) FROM b e + | START NODES (A) FROM a n JOIN ON e.id = n.id + | END NODES (A) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin) ) ddls(1) shouldEqual ddls.head @@ -285,36 +377,40 @@ class GraphDdlTest extends FunSpec with Matchers { it("allows these equivalent graph type definitions") { val ddls = List( // most compact form - GraphDdl(""" - |CREATE GRAPH TYPE myType ( - | A (x STRING), B (y STRING), C (z STRING), - | (A)-[B]->(C) - |) - |CREATE GRAPH myGraph OF myType () - """.stripMargin), + GraphDdl( + """ + |CREATE GRAPH TYPE myType ( + | A (x STRING), B (y STRING), C (z STRING), + | (A)-[B]->(C) + |) + |CREATE GRAPH myGraph OF myType () + """.stripMargin), // explicit node and rel type definitions - GraphDdl("""CREATE GRAPH TYPE myType ( - | A (x STRING), B (y STRING), C (z STRING), - | (A), (C), - | (A)-[B]->(C) - |) - |CREATE GRAPH myGraph OF myType () - """.stripMargin), + GraphDdl( + """CREATE GRAPH TYPE myType ( + | A (x STRING), B (y STRING), C (z STRING), + | (A), (C), + | (A)-[B]->(C) + |) + |CREATE GRAPH myGraph OF myType () + """.stripMargin), // implicit node type definitions - GraphDdl("""CREATE GRAPH TYPE myType ( - | A (x STRING), B (y STRING), C (z STRING), - | (A)-[B]->(C) - |) - |CREATE GRAPH myGraph OF myType () - """.stripMargin), + GraphDdl( + """CREATE GRAPH TYPE myType ( + | A (x STRING), B (y STRING), C (z STRING), + | (A)-[B]->(C) + |) + |CREATE GRAPH myGraph OF myType () + """.stripMargin), // shadowing - GraphDdl("""CREATE ELEMENT TYPE A (foo STRING) - |CREATE GRAPH TYPE myType ( - | A (x STRING), B (y STRING), C (z STRING), - | (A)-[B]->(C) - |) - |CREATE GRAPH myGraph OF myType () - """.stripMargin) + GraphDdl( + """CREATE ELEMENT TYPE A (foo STRING) + |CREATE GRAPH TYPE myType ( + | A (x STRING), B (y STRING), C (z STRING), + | (A)-[B]->(C) + |) + |CREATE GRAPH myGraph OF myType () + """.stripMargin) ) ddls(1) shouldEqual ddls.head @@ -322,137 +418,205 @@ class GraphDdlTest extends FunSpec with Matchers { ddls(3) shouldEqual ddls.head } - it("fails on duplicate node mappings") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |SET SCHEMA db.schema - | - |CREATE GRAPH TYPE fooSchema ( - | Person, - | (Person) - |) - |CREATE GRAPH fooGraph OF fooSchema ( - | (Person) FROM personView - | FROM personView - |) - """.stripMargin) - e.getFullMessage should (include("fooGraph") and include("(Person)") and include("personView")) + describe("failure handling") { + + it("fails on duplicate node mappings") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA db.schema + | + |CREATE GRAPH TYPE fooSchema ( + | Person, + | (Person) + |) + |CREATE GRAPH fooGraph OF fooSchema ( + | (Person) FROM personView + | FROM personView + |) + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("(Person)") and include("personView")) + } + + it("fails on duplicate relationship mappings") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA db.schema + | + |CREATE GRAPH TYPE fooSchema ( + | Person, + | KNOWS, + | (Person)-[KNOWS]->(Person) + |) + |CREATE GRAPH fooGraph OF fooSchema ( + | (Person)-[KNOWS]->(Person) + | FROM pkpView e + | START NODES (Person) FROM a n JOIN ON e.id = n.id + | END NODES (Person) FROM a n JOIN ON e.id = n.id, + | FROM pkpView e + | START NODES (Person) FROM a n JOIN ON e.id = n.id + | END NODES (Person) FROM a n JOIN ON e.id = n.id + |) + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("(Person)-[KNOWS]->(Person)") and include("pkpView")) + } + + it("fails on duplicate global labels") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE ELEMENT TYPE Person + |CREATE ELEMENT TYPE Person + """.stripMargin) + e.getFullMessage should include("Person") + } + + it("fails on duplicate local labels") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema ( + | Person, + | Person + |) + """.stripMargin) + e.getFullMessage should (include("fooSchema") and include("Person")) + } + + it("fails on duplicate graph types") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema () + |CREATE GRAPH TYPE fooSchema () + """.stripMargin) + e.getFullMessage should include("fooSchema") + } + + it("fails on duplicate graphs") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema () + |CREATE GRAPH fooGraph OF fooSchema () + |CREATE GRAPH fooGraph OF fooSchema () + """.stripMargin) + e.getFullMessage should include("fooGraph") + } + + it("fails on unresolved graph type") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema () + |CREATE GRAPH fooGraph OF barSchema () + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("fooSchema") and include("barSchema")) + } + + it("fails on unresolved labels") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema ( + | Person1, + | Person2, + | (Person3, Person4) + |) + """.stripMargin) + e.getFullMessage should (include("fooSchema") and include("Person3") and include("Person4")) + } + + it("fails on unresolved labels in mapping") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH fooGraph ( + | Person1, + | Person2, + | (Person3, Person4) FROM x + |) + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("Person3") and include("Person4")) + } + + it("fails on incompatible property types") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |CREATE GRAPH TYPE fooSchema ( + | Person1 ( age STRING ) , + | Person2 ( age INTEGER ) , + | (Person1, Person2) + |) + """.stripMargin) + e.getFullMessage should (include("fooSchema") and include("Person1") and include("Person2") and include("age") and include("STRING") and include("INTEGER")) + } + + it("fails on unresolved property names") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA a.b + |CREATE GRAPH TYPE fooSchema ( + | Person ( age1 STRING ) , + | (Person) + |) + |CREATE GRAPH fooGraph OF fooSchema ( + | (Person) FROM personView ( person_name AS age2 ) + |) + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("Person") and include("personView") and include("age1") and include("age2")) + } + + it("fails on unresolved inherited element types") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA a.b + |CREATE GRAPH TYPE fooSchema ( + | Person ( name STRING ) , + | Employee EXTENDS MissingPerson ( dept STRING ) , + | (Employee) + |) + |CREATE GRAPH fooGraph OF fooSchema ( + | (Employee) FROM employeeView ( person_name AS name, emp_dept AS dept ) + |) + """.stripMargin) + e.getFullMessage should (include("fooSchema") and include("Employee") and include("MissingPerson")) + } + + it("fails on unresolved inherited element types within inlined graph type") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA a.b + |CREATE GRAPH fooGraph ( + | Person ( name STRING ), + | Employee EXTENDS MissingPerson ( dept STRING ), + | + | (Employee) FROM employeeView ( person_name AS name, emp_dept AS dept ) + |) + """.stripMargin) + e.getFullMessage should (include("fooGraph") and include("Employee") and include("MissingPerson")) + } + + it("fails on cyclic element type inheritance") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA a.b + |CREATE GRAPH fooGraph ( + | A EXTENDS B ( a STRING ), + | B EXTENDS A ( b STRING ), + | + | (A) FROM a ( A_a AS a, B_b AS b ), + | (B) FROM b ( A_a AS a, B_b AS b ) + |) + """.stripMargin) + e.getFullMessage should (include("Circular dependency") and include("A -> B -> A")) + } + + it("fails on conflicting property types in inheritance hierarchy") { + val e = the[GraphDdlException] thrownBy GraphDdl( + """ + |SET SCHEMA a.b + |CREATE GRAPH fooGraph ( + | A ( x STRING ), + | B ( x INTEGER ), + | C EXTENDS A, B (), + | + | (C) + |) + """.stripMargin) + e.getFullMessage should (include("(A,B,C)") and include("x") and include("INTEGER") and include("STRING")) + } } - - it("fails on duplicate relationship mappings") { - val e = the[GraphDdlException] thrownBy GraphDdl( - """ - |SET SCHEMA db.schema - | - |CREATE GRAPH TYPE fooSchema ( - | Person, - | KNOWS, - | (Person)-[KNOWS]->(Person) - |) - |CREATE GRAPH fooGraph OF fooSchema ( - | (Person)-[KNOWS]->(Person) - | FROM pkpView e - | START NODES (Person) FROM a n JOIN ON e.id = n.id - | END NODES (Person) FROM a n JOIN ON e.id = n.id, - | FROM pkpView e - | START NODES (Person) FROM a n JOIN ON e.id = n.id - | END NODES (Person) FROM a n JOIN ON e.id = n.id - |) - """.stripMargin) - e.getFullMessage should (include("fooGraph") and include("(Person)-[KNOWS]->(Person)") and include("pkpView")) - } - - it("fails on duplicate global labels") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE ELEMENT TYPE Person - |CREATE ELEMENT TYPE Person - """.stripMargin) - e.getFullMessage should include("Person") - } - - it("fails on duplicate local labels") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema ( - | Person, - | Person - |) - """.stripMargin) - e.getFullMessage should (include("fooSchema") and include("Person")) - } - - it("fails on duplicate graph types") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema () - |CREATE GRAPH TYPE fooSchema () - """.stripMargin) - e.getFullMessage should include("fooSchema") - } - - it("fails on duplicate graphs") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema () - |CREATE GRAPH fooGraph OF fooSchema () - |CREATE GRAPH fooGraph OF fooSchema () - """.stripMargin) - e.getFullMessage should include("fooGraph") - } - - it("fails on unresolved graph type") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema () - |CREATE GRAPH fooGraph OF barSchema () - """.stripMargin) - e.getFullMessage should (include("fooGraph") and include("fooSchema") and include("barSchema")) - } - - it("fails on unresolved labels") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema ( - | Person1, - | Person2, - | (Person3, Person4) - |) - """.stripMargin) - e.getFullMessage should (include("fooSchema") and include("Person3") and include("Person4")) - } - - it("fails on unresolved labels in mapping") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH fooGraph ( - | Person1, - | Person2, - | (Person3, Person4) FROM x - |) - """.stripMargin) - e.getFullMessage should (include("fooGraph") and include("Person3") and include("Person4")) - } - - it("fails on incompatible property types") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |CREATE GRAPH TYPE fooSchema ( - | Person1 ( age STRING ) , - | Person2 ( age INTEGER ) , - | (Person1, Person2) - |) - """.stripMargin) - e.getFullMessage should ( - include("fooSchema") and include("Person1") and include("Person2") and include("age") and include("STRING") and include("INTEGER") - ) - } - - it("fails on unresolved property names") { - val e = the [GraphDdlException] thrownBy GraphDdl(""" - |SET SCHEMA a.b - |CREATE GRAPH TYPE fooSchema ( - | Person ( age1 STRING ) , - | (Person) - |) - |CREATE GRAPH fooGraph OF fooSchema ( - | (Person) FROM personView ( person_name AS age2 ) - |) - """.stripMargin) - e.getFullMessage should ( - include("fooGraph") and include("Person") and include("personView") and include("age1") and include("age2") - ) - } - }