From 3ebb49fbfc4e9817db6ee6f95445bcb177e9d74c Mon Sep 17 00:00:00 2001 From: Gem Lamont <106068376+gem-neo4j@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:04:10 +0100 Subject: [PATCH] [7nVhdJu3] apoc.convert.toTree keep all rels in tree fix (#581) --- core/src/main/java/apoc/convert/Json.java | 18 +- .../java/apoc/convert/ConvertJsonTest.java | 270 ++++++++++++------ 2 files changed, 197 insertions(+), 91 deletions(-) diff --git a/core/src/main/java/apoc/convert/Json.java b/core/src/main/java/apoc/convert/Json.java index 3aced6fdb..99991bd82 100644 --- a/core/src/main/java/apoc/convert/Json.java +++ b/core/src/main/java/apoc/convert/Json.java @@ -170,14 +170,12 @@ public List fromJsonList( } @Procedure("apoc.convert.toTree") - @Description( - "Returns a stream of `MAP` values, representing the given `PATH` values as a tree with at least one root.") - // todo optinally provide root node + @Description("Returns a stream of `MAP` values, representing the given `PATH` values as a tree with at least one root.") public Stream toTree( @Name("paths") List paths, @Name(value = "lowerCaseRels", defaultValue = "true") boolean lowerCaseRels, @Name(value = "config", defaultValue = "{}") Map config) { - if (paths.isEmpty()) return Stream.of(new MapResult(Collections.emptyMap())); + if (paths == null || paths.isEmpty()) return Stream.of(new MapResult(Collections.emptyMap())); ConvertConfig conf = new ConvertConfig(config); Map> nodes = conf.getNodes(); Map> rels = conf.getRels(); @@ -203,8 +201,12 @@ public Stream toTree( // parent-[:HAS_CHILD]->(child) vs. (parent)<-[:PARENT_OF]-(child) if (!nMap.containsKey(typeName)) nMap.put(typeName, new ArrayList<>(16)); List> list = (List) nMap.get(typeName); + // Check that this combination of rel and node doesn't already exist Optional> optMap = list.stream() - .filter(elem -> elem.get("_id").equals(m.getId())) + .filter(elem -> + elem.get("_elementId").equals(m.getElementId()) + && elem.get(typeName + "._elementId").equals(r.getElementId()) + ) .findFirst(); if (!optMap.isPresent()) { Map mMap = toMap(m, nodes); @@ -255,9 +257,10 @@ public String toSortedJsonMap( private Map addRelProperties( Map mMap, String typeName, Relationship r, Map> relFilters) { + String prefix = typeName + "."; + mMap.put(prefix + "_elementId", r.getElementId()); Map rProps = r.getAllProperties(); if (rProps.isEmpty()) return mMap; - String prefix = typeName + "."; if (relFilters.containsKey(typeName)) { rProps = filterProperties(rProps, relFilters.get(typeName)); } @@ -269,7 +272,8 @@ private Map toMap(Node n, Map> nodeFilters) Map props = n.getAllProperties(); Map result = new LinkedHashMap<>(props.size() + 2); String type = Util.labelString(n); - result.put("_id", n.getId()); + result.put("_id", n.getId());; + result.put("_elementId", n.getElementId()); result.put("_type", type); if (nodeFilters.containsKey(type)) { // Check if list contains LABEL props = filterProperties(props, nodeFilters.get(type)); diff --git a/core/src/test/java/apoc/convert/ConvertJsonTest.java b/core/src/test/java/apoc/convert/ConvertJsonTest.java index fc741fccb..c745c55aa 100644 --- a/core/src/test/java/apoc/convert/ConvertJsonTest.java +++ b/core/src/test/java/apoc/convert/ConvertJsonTest.java @@ -30,8 +30,13 @@ import apoc.util.MapUtil; import apoc.util.TestUtil; import apoc.util.Util; + +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + import junit.framework.TestCase; import org.apache.commons.lang3.exception.ExceptionUtils; import org.assertj.core.util.Arrays; @@ -41,8 +46,10 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.neo4j.graphdb.Entity; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.graphdb.Result; import org.neo4j.test.rule.DbmsRule; @@ -445,44 +452,51 @@ public void testToTreeIssue1685() { testCall( db, - "match path = (k:Person {name:'Keanu Reeves'})-[*..5]-(x) " + "with collect(path) as paths " - + "call apoc.convert.toTree(paths) yield value " - + "return value", + """ + MATCH path = (k:Person {name:'Keanu Reeves'})-[*..5]-(x) + WITH collect(path) AS paths + CALL apoc.convert.toTree(paths) + YIELD value + RETURN value""", (row) -> { - Map root = (Map) row.get("value"); + Map root = (Map) row.get("value"); assertEquals("Person", root.get("_type")); List actedInList = (List) root.get("acted_in"); assertEquals(7, actedInList.size()); List innerList = (List) ((Map) actedInList.get(1)).get("acted_in"); - assertEquals(6, ((Map) innerList.get(0)).size()); + assertEquals(8, ((Map) innerList.get(0)).size()); }); } @Test public void testToTreeIssue2190() { - db.executeTransactionally("CREATE (root:TreeNode {name:'root'})\n" + "CREATE (c0:TreeNode {name: 'c0'})\n" - + "CREATE (c1:TreeNode {name: 'c1'})\n" - + "CREATE (c2:TreeNode {name: 'c2'})\n" - + "CREATE (c00:TreeNode {name : 'c00'})\n" - + "CREATE (c01:TreeNode {name : 'c01'})\n" - + "CREATE (c10:TreeNode {name : 'c10'})\n" - + "CREATE (c100:TreeNode {name : 'c100'})\n" - + "CREATE (root)-[:CHILD {order: 0}]->(c0)\n" - + "CREATE (root)-[:CHILD {order: 1}]->(c1)\n" - + "CREATE (root)-[:CHILD { order: 2}]->(c2)\n" - + "CREATE (c0)-[:CHILD {order: 0}]->(c00)\n" - + "CREATE (c0)-[:CHILD {order: 1}]->(c01)\n" - + "CREATE (c1)-[:CHILD {order: 0}]->(c10)\n" - + "CREATE (c10)-[:CHILD {order: 0}]->(c100)"); + db.executeTransactionally(""" + CREATE (root:TreeNode {name:'root'}) + CREATE (c0:TreeNode {name: 'c0'}) + CREATE (c1:TreeNode {name: 'c1'}) + CREATE (c2:TreeNode {name: 'c2'}) + CREATE (c00:TreeNode {name : 'c00'}) + CREATE (c01:TreeNode {name : 'c01'}) + CREATE (c10:TreeNode {name : 'c10'}) + CREATE (c100:TreeNode {name : 'c100'}) + CREATE (root)-[:CHILD {order: 0}]->(c0) + CREATE (root)-[:CHILD {order: 1}]->(c1) + CREATE (root)-[:CHILD { order: 2}]->(c2) + CREATE (c0)-[:CHILD {order: 0}]->(c00) + CREATE (c0)-[:CHILD {order: 1}]->(c01) + CREATE (c1)-[:CHILD {order: 0}]->(c10) + CREATE (c10)-[:CHILD {order: 0}]->(c100)"""); testCall( db, - "MATCH(root:TreeNode) WHERE root.name = \"root\"\n" + "MATCH path = (root)-[cl:CHILD*]->(c:TreeNode)\n" - + "WITH path, [r IN relationships(path) | r.order] AS orders\n" - + "ORDER BY orders\n" - + "WITH COLLECT(path) AS paths\n" - + "CALL apoc.convert.toTree(paths, true, {sortPaths: false}) YIELD value AS tree\n" - + "RETURN tree", + """ + MATCH(root:TreeNode) WHERE root.name = "root" + MATCH path = (root)-[cl:CHILD*]->(c:TreeNode) + WITH path, [r IN relationships(path) | r.order] AS orders + ORDER BY orders + WITH COLLECT(path) AS paths + CALL apoc.convert.toTree(paths, true, {sortPaths: false}) YIELD value AS tree + RETURN tree""", (row) -> { Map tree = (Map) row.get("tree"); final List child = (List) tree.get("child"); @@ -541,16 +555,19 @@ public void testTreeOfEmptyList() { @Test public void testToTreeLeafNodes() { - String createStatement = "CREATE\n" + " (c1:Category {name: 'PC'}),\n" - + " (c1)-[:subcategory {id:1}]->(c2:Category {name: 'Parts'}),\n" - + " (c2)-[:subcategory {id:2}]->(c3:Category {name: 'CPU'})"; + String createStatement = """ + CREATE + (c1:Category {name: 'PC'}), + (c1)-[:subcategory {id:1}]->(c2:Category {name: 'Parts'}), + (c2)-[:subcategory {id:2}]->(c3:Category {name: 'CPU'})"""; db.executeTransactionally(createStatement); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps) yield value + RETURN value;"""; testCall(db, call, (row) -> { Map root = (Map) row.get("value"); @@ -588,35 +605,63 @@ public void testToJsonMapSortingPropertiesIgnoringCase() { @Test public void testToTreeParentNodes() { String createDatabase = - "CREATE (b:Bib {id: '57523a6f-fda9-4a61-c4f6-08d47cdcf0cd', langId: 2})-[:HAS]->(c:Comm {id: 'a34fd608-1751-0b5d-cb38-6991297fa9c9', langId: 2}), \n" - + "(b)-[:HAS]->(c1:Comm {id: 'a34fd608-262b-678a-cb38-6991297fa9c8', langId: 2}),\n" - + "(u:User {id: 'facebook|680594762097202'})-[:Flag {Created: '2018-11-21T11:22:01', FlagType: 4}]->(c1),\n" - + "(u)-[:Flag {Created: '2018-11-21T11:22:04', FlagType: 5}]->(c),\n" - + "(u1:User {id: 'google-oauth2|106707535753175966005'})-[:Flag {Created: '2018-11-21T11:20:34', FlagType: 2}]->(c),\n" - + "(u1)-[:Flag {Created: '2018-11-21T11:20:31', FlagType: 1}]->(c1)"; + """ + CREATE (b:Bib {id: '57523a6f-fda9-4a61-c4f6-08d47cdcf0cd', langId: 2})-[:HAS {id: "rel1"}]->(c:Comm {id: 'a34fd608-1751-0b5d-cb38-6991297fa9c9', langId: 2}), + (b)-[:HAS {id: "rel2"}]->(c1:Comm {id: 'a34fd608-262b-678a-cb38-6991297fa9c8', langId: 2}), + (u:User {id: 'facebook|680594762097202'})-[:Flag {id: "rel3", Created: '2018-11-21T11:22:01', FlagType: 4}]->(c1), + (u)-[:Flag {id: "rel4", Created: '2018-11-21T11:22:04', FlagType: 5}]->(c), + (u1:User {id: 'google-oauth2|106707535753175966005'})-[:Flag {id: "rel5", Created: '2018-11-21T11:20:34', FlagType: 2}]->(c), + (u1)-[:Flag {id: "rel6", Created: '2018-11-21T11:20:31', FlagType: 1}]->(c1)"""; db.executeTransactionally(createDatabase); - String call = "MATCH (parent:Bib {id: '57523a6f-fda9-4a61-c4f6-08d47cdcf0cd'})\n" + "WITH parent\n" - + "OPTIONAL MATCH childFlagPath=(parent)-[:HAS]->(:Comm)<-[:Flag]-(:User)\n" - + "WITH COLLECT(childFlagPath) AS cfp\n" - + "CALL apoc.convert.toTree(cfp) yield value\n" - + "RETURN value"; + String queryToGetGeneratedIds = """ + MATCH (n) + RETURN n.id as givenId, id(n) as id, elementId(n) as elementId + UNION + MATCH ()-[r]->() + RETURN r.id as givenId, id(r) as id, elementId(r) as elementId + """; + + db.executeTransactionally(queryToGetGeneratedIds); + Map> generatedIdsMapping = db.executeTransactionally( + queryToGetGeneratedIds, + Collections.emptyMap(), + result -> result.stream() + .collect( + Collectors.toMap( + row -> row.get("givenId").toString(), + row -> Map.of( + "id", row.get("id").toString(), + "elementId", row.get("elementId").toString() + ) + ) + )); + + String call = """ + MATCH (parent:Bib {id: '57523a6f-fda9-4a61-c4f6-08d47cdcf0cd'}) + WITH parent + OPTIONAL MATCH childFlagPath=(parent)-[:HAS]->(:Comm)<-[:Flag]-(:User) + WITH COLLECT(childFlagPath) AS cfp + CALL apoc.convert.toTree(cfp) yield value + RETURN value"""; testCall(db, call, (row) -> { - Map root = (Map) row.get("value"); + Map root = (Map) row.get("value"); assertEquals("Bib", root.get("_type")); - assertEquals(0L, root.get("_id")); + assertEquals(generatedIdsMapping.get("57523a6f-fda9-4a61-c4f6-08d47cdcf0cd").get("id"), root.get("_id").toString()); + assertEquals(generatedIdsMapping.get("57523a6f-fda9-4a61-c4f6-08d47cdcf0cd").get("elementId"), root.get("_elementId").toString()); assertEquals("57523a6f-fda9-4a61-c4f6-08d47cdcf0cd", root.get("id")); assertEquals(2L, root.get("langId")); List has = (List) root.get("has"); // HAS REL assertEquals(2, has.size()); - Map hasPart = has.get(0); + Map hasPart = has.get(0); assertEquals("Comm", hasPart.get("_type")); - assertEquals(2L, hasPart.get("_id")); + assertEquals(generatedIdsMapping.get("a34fd608-262b-678a-cb38-6991297fa9c8").get("id"), hasPart.get("_id").toString()); + assertEquals(generatedIdsMapping.get("a34fd608-262b-678a-cb38-6991297fa9c8").get("elementId"), hasPart.get("_elementId").toString()); assertEquals("a34fd608-262b-678a-cb38-6991297fa9c8", hasPart.get("id")); assertEquals(2L, hasPart.get("langId")); List subParts = (List) hasPart.get("flag"); @@ -628,8 +673,12 @@ public void testToTreeParentNodes() { Matchers.hasItem(MapUtil.map( "_type", "User", "flag.Created", "2018-11-21T11:22:01", - "_id", 3L, + "_id", Long.parseLong(generatedIdsMapping.get("facebook|680594762097202").get("id")), + "_elementId", generatedIdsMapping.get("facebook|680594762097202").get("elementId"), "id", "facebook|680594762097202", + "flag.id", "rel3", + "flag._elementId", generatedIdsMapping.get("rel3").get("elementId"), + "flag.id", "rel3", "flag.FlagType", 4L))); MatcherAssert.assertThat( @@ -637,13 +686,16 @@ public void testToTreeParentNodes() { Matchers.hasItem(MapUtil.map( "_type", "User", "flag.Created", "2018-11-21T11:20:31", - "_id", 4L, + "_id", Long.parseLong(generatedIdsMapping.get("google-oauth2|106707535753175966005").get("id")), + "_elementId", generatedIdsMapping.get("google-oauth2|106707535753175966005").get("elementId"), "id", "google-oauth2|106707535753175966005", + "flag.id", "rel6", + "flag._elementId", generatedIdsMapping.get("rel6").get("elementId"), "flag.FlagType", 1L))); hasPart = has.get(1); assertEquals("Comm", hasPart.get("_type")); - assertEquals(1L, hasPart.get("_id")); + assertEquals(Long.parseLong(generatedIdsMapping.get("a34fd608-1751-0b5d-cb38-6991297fa9c9").get("id")), hasPart.get("_id")); assertEquals("a34fd608-1751-0b5d-cb38-6991297fa9c9", hasPart.get("id")); assertEquals(2L, hasPart.get("langId")); }); @@ -652,11 +704,12 @@ public void testToTreeParentNodes() { @Test public void testToTreeLeafNodesWithConfigInclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name']}, rels: {subcategory:['id']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name']}, rels: {subcategory:['id']}}) yield value + RETURN value;"""; testCall(db, call, (row) -> { Map root = (Map) row.get("value"); assertEquals("Category", root.get("_type")); @@ -681,11 +734,12 @@ public void testToTreeLeafNodesWithConfigInclude() { @Test public void testToTreeLeafNodesWithConfigExclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true,{nodes: {Category: ['-name']}, rels: {subcategory:['-id']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true,{nodes: {Category: ['-name']}, rels: {subcategory:['-id']}}) yield value + RETURN value;"""; testCall(db, call, (row) -> { Map root = (Map) row.get("value"); assertEquals("Category", root.get("_type")); @@ -710,11 +764,12 @@ public void testToTreeLeafNodesWithConfigExclude() { @Test public void testToTreeLeafNodesWithConfigExcludeInclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name']}, rels: {subcategory:['-id']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name']}, rels: {subcategory:['-id']}}) yield value + RETURN value;"""; testCall(db, call, (row) -> { Map root = (Map) row.get("value"); assertEquals("Category", root.get("_type")); @@ -739,11 +794,12 @@ public void testToTreeLeafNodesWithConfigExcludeInclude() { @Test public void testToTreeLeafNodesWithConfigOnlyInclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name', 'surname']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['name', 'surname']}}) yield value + RETURN value;"""; testCall(db, call, (row) -> { Map root = (Map) row.get("value"); assertEquals("Category", root.get("_type")); @@ -768,11 +824,12 @@ public void testToTreeLeafNodesWithConfigOnlyInclude() { @Test public void testToTreeLeafNodesWithConfigErrorInclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['-name','name']}, rels: {subcategory:['-id']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['-name','name']}, rels: {subcategory:['-id']}}) yield value + RETURN value;"""; try { testResult(db, call, Result::close); } catch (QueryExecutionException e) { @@ -782,14 +839,57 @@ public void testToTreeLeafNodesWithConfigErrorInclude() { } } + @Test + public void testToTreeDoesNotRemoveNonDuplicateRels() { + String createStatement = """ + CREATE (v1:N {id: 'n21', name: 'Node 21', p2: 'node21'}) + CREATE (v2:N {id: 'n22', name: 'Node 22', p2: 'node22'}) + CREATE (v1)-[:R {prop1: 'n21->n22 [1]', prop2: 'Rel 1'}]->(v2) + CREATE (v1)-[:R {prop1: 'n21->n22 [2]', prop2: 'Rel 2'}]->(v2)"""; + + db.executeTransactionally(createStatement); + + String query = """ + MATCH p1 = (n:N {id:'n21'})-[e1]->(m1:N) + WITH COLLECT(p1) as paths + CALL apoc.convert.toTree(paths, false) + YIELD value + RETURN value"""; + + testResult( + db, + query, + res -> { + Map value = (Map) res.next().get("value"); + // Check Parent Node: (v1:T1 {id: 'v21', name: 'Vertex 21', p2: 'value21'}) + assertEquals("n21", value.get("id")); + assertEquals("N", value.get("_type")); + assertEquals("node21", value.get("p2")); + assertEquals("Node 21", value.get("name")); + // Check Children Nodes + List> RBranches = (List>) value.get("R"); + Map rel1 = Map.of("p2", "node22", "name", "Node 22", "id", "n22", "R.prop1", "n21->n22 [1]", "R.prop2", "Rel 1"); + Map rel2 = Map.of("p2", "node22", "name", "Node 22", "id", "n22", "R.prop1", "n21->n22 [2]", "R.prop2", "Rel 2"); + assertEquals(2, RBranches.size()); + if (RBranches.get(0).entrySet().containsAll(rel1.entrySet())) { + assertTrue(RBranches.get(1).entrySet().containsAll(rel2.entrySet())); + } else { + assertTrue(RBranches.get(0).entrySet().containsAll(rel1.entrySet())); + assertTrue(RBranches.get(1).entrySet().containsAll(rel2.entrySet())); + } + assertFalse(res.hasNext()); + }); + } + @Test public void testToTreeLeafNodesWithConfigErrorExclude() { statementForConfig(db); - String call = "MATCH p=(n:Category)-[:subcategory*]->(m)\n" - + "WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n)\n" - + "WITH COLLECT(p) AS ps\n" - + "CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['-name']}, rels: {subcategory:['-id','name']}}) yield value\n" - + "RETURN value;"; + String call = """ + MATCH p=(n:Category)-[:subcategory*]->(m) + WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) + WITH COLLECT(p) AS ps + CALL apoc.convert.toTree(ps, true, {nodes: {Category: ['-name']}, rels: {subcategory:['-id','name']}}) yield value + RETURN value;"""; try { testResult(db, call, Result::close); } catch (QueryExecutionException e) { @@ -800,9 +900,11 @@ public void testToTreeLeafNodesWithConfigErrorExclude() { } private static void statementForConfig(GraphDatabaseService db) { - String createStatement = "CREATE\n" + " (c1:Category {name: 'PC', surname: 'computer'}),\n" - + " (c1)-[:subcategory {id:1, subCat: 'gen'}]->(c2:Category {name: 'Parts'}),\n" - + " (c2)-[:subcategory {id:2, subCat: 'ex'}]->(c3:Category {name: 'CPU'})"; + String createStatement = """ + CREATE + (c1:Category {name: 'PC', surname: 'computer'}), + (c1)-[:subcategory {id:1, subCat: 'gen'}]->(c2:Category {name: 'Parts'}), + (c2)-[:subcategory {id:2, subCat: 'ex'}]->(c3:Category {name: 'CPU'})"""; db.executeTransactionally(createStatement); }