From 1b93708c7e40b080321d315930b063bd3b5c8d2c Mon Sep 17 00:00:00 2001 From: Gem Lamont <106068376+gem-neo4j@users.noreply.github.com> Date: Mon, 3 Feb 2025 08:56:29 +0100 Subject: [PATCH] Add ability to test different cypher versions globally (#725) --- common/src/main/java/apoc/util/Util.java | 12 + .../java/apoc/ArgumentDescriptionsTest.java | 12 +- .../java/apoc/convert/ConvertJsonTest.java | 29 +- .../apoc/convert/PathsToJsonTreeTest.java | 1 + .../src/test/java/apoc/cypher/CypherTest.java | 2 - .../export/ImportAndLoadCoreSecurityTest.java | 4 +- .../java/apoc/export/SecurityTestUtil.java | 2 + .../java/apoc/export/arrow/ArrowTest.java | 9 +- .../export/arrow/ExportArrowSecurityTest.java | 9 +- core/src/test/java/apoc/help/HelpTest.java | 40 +- .../src/test/java/apoc/load/LoadJsonTest.java | 6 +- core/src/test/java/apoc/map/MapsTest.java | 2 +- core/src/test/java/apoc/meta/MetaTest.java | 12 +- .../test/java/apoc/schema/SchemasTest.java | 616 +++++++++++------- core/src/test/java/apoc/text/StringsTest.java | 10 +- .../apoc/trigger/TriggerDisabledTest.java | 9 +- .../test/java/apoc/trigger/TriggerTest.java | 10 +- .../src/test/java/apoc/warmup/WarmupTest.java | 9 +- .../java/apoc/it/core/ApocVersionsTest.java | 4 +- .../java/org/neo4j/test/rule/DbmsRule.java | 14 + 20 files changed, 518 insertions(+), 294 deletions(-) diff --git a/common/src/main/java/apoc/util/Util.java b/common/src/main/java/apoc/util/Util.java index ffd8538dd..c71b6a9a4 100644 --- a/common/src/main/java/apoc/util/Util.java +++ b/common/src/main/java/apoc/util/Util.java @@ -111,6 +111,7 @@ import org.neo4j.graphdb.security.URLAccessChecker; import org.neo4j.graphdb.security.URLAccessValidationError; import org.neo4j.internal.schema.ConstraintDescriptor; +import org.neo4j.kernel.api.QueryLanguage; import org.neo4j.kernel.impl.coreapi.InternalTransaction; import org.neo4j.kernel.impl.util.ValueUtils; import org.neo4j.kernel.internal.GraphDatabaseAPI; @@ -1361,4 +1362,15 @@ public static T withBackOffRetries(Supplier func, long initialTimeout, lo } return result; } + + // Get the current supported query language versions, if this list changes + // this function will error, on error please update! + public static List getCypherVersions() { + return QueryLanguage.ALL.stream() + .map(l -> switch (l) { + case QueryLanguage.CYPHER_5 -> "5"; + case QueryLanguage.CYPHER_25 -> "25"; + }) + .toList(); + } } diff --git a/core/src/test/java/apoc/ArgumentDescriptionsTest.java b/core/src/test/java/apoc/ArgumentDescriptionsTest.java index 5f5d1fc62..9a4e1bce8 100644 --- a/core/src/test/java/apoc/ArgumentDescriptionsTest.java +++ b/core/src/test/java/apoc/ArgumentDescriptionsTest.java @@ -106,7 +106,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.cypher.internal.CypherVersion; import org.neo4j.test.rule.DbmsRule; @@ -117,8 +116,7 @@ public class ArgumentDescriptionsTest { @Rule public DbmsRule db = new ImpermanentDbmsRule() - .withSetting(GraphDatabaseSettings.procedure_unrestricted, singletonList("apoc.*")) - .withSetting(GraphDatabaseInternalSettings.enable_experimental_cypher_versions, true); + .withSetting(GraphDatabaseSettings.procedure_unrestricted, singletonList("apoc.*")); @Before public void setUp() { @@ -202,9 +200,11 @@ public void teardown() { @Test public void functionArgumentDescriptionsDefaultVersion() throws IOException { + // TODO: When Cypher command for getting default is available use that here + // for now we just force this to use the preset default assertResultAsJsonEquals( CypherVersion.Default, - showFunctions(null), + showFunctions(CypherVersion.Default), "/functions/cypher%s/functions.json".formatted(CypherVersion.Default), "/functions/common/functions.json"); } @@ -223,9 +223,11 @@ public void functionArgumentDescriptions() throws IOException { @Test public void procedureArgumentDescriptionsDefaultVersion() throws IOException { + // TODO: When Cypher command for getting default is available use that here + // for now we just force this to use the preset default assertResultAsJsonEquals( CypherVersion.Default, - showProcedures(null), + showProcedures(CypherVersion.Default), "/procedures/cypher%s/procedures.json".formatted(CypherVersion.Default), "/procedures/common/procedures.json"); } diff --git a/core/src/test/java/apoc/convert/ConvertJsonTest.java b/core/src/test/java/apoc/convert/ConvertJsonTest.java index af3fa69d4..faf2b3128 100644 --- a/core/src/test/java/apoc/convert/ConvertJsonTest.java +++ b/core/src/test/java/apoc/convert/ConvertJsonTest.java @@ -448,6 +448,7 @@ public void testToTreeIssue1685() { testCall( db, """ + CYPHER 5 MATCH path = (k:Person {name:'Keanu Reeves'})-[*..5]-(x) WITH collect(path) AS paths CALL apoc.convert.toTree(paths) @@ -486,6 +487,7 @@ public void testToTreeIssue2190() { testCall( db, """ + CYPHER 5 MATCH(root:TreeNode) WHERE root.name = "root" MATCH path = (root)-[cl:CHILD*]->(c:TreeNode) WITH path, [r IN relationships(path) | r.order] AS orders @@ -507,9 +509,11 @@ WITH COLLECT(path) AS paths public void testToTree() { testCall( db, - "CREATE p1=(m:Movie {title:'M'})<-[:ACTED_IN {role:'R1'}]-(:Actor {name:'A1'}), " - + " p2 = (m)<-[:ACTED_IN {role:'R2'}]-(:Actor {name:'A2'}) WITH [p1,p2] as paths " - + " CALL apoc.convert.toTree(paths) YIELD value RETURN value", + """ + CYPHER 5 CREATE p1=(m:Movie {title:'M'})<-[:ACTED_IN {role:'R1'}]-(:Actor {name:'A1'}), + p2 = (m)<-[:ACTED_IN {role:'R2'}]-(:Actor {name:'A2'}) WITH [p1,p2] as paths + CALL apoc.convert.toTree(paths) YIELD value RETURN value + """, (row) -> { Map root = (Map) row.get("value"); assertEquals("Movie", root.get("_type")); @@ -526,9 +530,11 @@ public void testToTree() { public void testToTreeUpperCaseRels() { testCall( db, - "CREATE p1=(m:Movie {title:'M'})<-[:ACTED_IN {role:'R1'}]-(:Actor {name:'A1'}), " - + " p2 = (m)<-[:ACTED_IN {role:'R2'}]-(:Actor {name:'A2'}) WITH [p1,p2] as paths " - + " CALL apoc.convert.toTree(paths,false) YIELD value RETURN value", + """ + CYPHER 5 + CREATE p1=(m:Movie {title:'M'})<-[:ACTED_IN {role:'R1'}]-(:Actor {name:'A1'}), + p2 = (m)<-[:ACTED_IN {role:'R2'}]-(:Actor {name:'A2'}) WITH [p1,p2] as paths + CALL apoc.convert.toTree(paths,false) YIELD value RETURN value""", (row) -> { Map root = (Map) row.get("value"); assertEquals("Movie", root.get("_type")); @@ -543,7 +549,7 @@ public void testToTreeUpperCaseRels() { @Test public void testTreeOfEmptyList() { - testCall(db, " CALL apoc.convert.toTree([]) YIELD value RETURN value", (row) -> { + testCall(db, "CYPHER 5 CALL apoc.convert.toTree([]) YIELD value RETURN value", (row) -> { Map root = (Map) row.get("value"); assertTrue(root.isEmpty()); }); @@ -561,6 +567,7 @@ public void testToTreeLeafNodes() { String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -632,6 +639,7 @@ RETURN r.id as givenId, id(r) as id, elementId(r) as elementId String call = """ + CYPHER 5 MATCH (parent:Bib {id: '57523a6f-fda9-4a61-c4f6-08d47cdcf0cd'}) WITH parent OPTIONAL MATCH childFlagPath=(parent)-[:HAS]->(:Comm)<-[:Flag]-(:User) @@ -747,6 +755,7 @@ public void testToTreeLeafNodesWithConfigInclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -778,6 +787,7 @@ public void testToTreeLeafNodesWithConfigExclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -809,6 +819,7 @@ public void testToTreeLeafNodesWithConfigExcludeInclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -840,6 +851,7 @@ public void testToTreeLeafNodesWithConfigOnlyInclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -871,6 +883,7 @@ public void testToTreeLeafNodesWithConfigErrorInclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps @@ -898,6 +911,7 @@ public void testToTreeDoesNotRemoveNonDuplicateRels() { String query = """ + CYPHER 5 MATCH p1 = (n:N {id:'n21'})-[e1]->(m1:N) WITH COLLECT(p1) as paths CALL apoc.convert.toTree(paths, false) @@ -933,6 +947,7 @@ public void testToTreeLeafNodesWithConfigErrorExclude() { statementForConfig(db); String call = """ + CYPHER 5 MATCH p=(n:Category)-[:subcategory*]->(m) WHERE NOT (m)-[:subcategory]->() AND NOT ()-[:subcategory]->(n) WITH COLLECT(p) AS ps diff --git a/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java b/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java index c537249a3..6da58b897 100644 --- a/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java +++ b/core/src/test/java/apoc/convert/PathsToJsonTreeTest.java @@ -703,6 +703,7 @@ public void testToTreeMultiLabelFiltersForOldProcedure() { var query = """ + CYPHER 5 MATCH path = (n)-[r]->(m) WITH COLLECT(path) AS paths CALL apoc.convert.toTree(paths, true, {nodes: { A: ['-nodeName'] } }) YIELD value AS tree diff --git a/core/src/test/java/apoc/cypher/CypherTest.java b/core/src/test/java/apoc/cypher/CypherTest.java index bb38a5655..094dcaaa1 100644 --- a/core/src/test/java/apoc/cypher/CypherTest.java +++ b/core/src/test/java/apoc/cypher/CypherTest.java @@ -61,7 +61,6 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; -import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.QueryExecutionException; @@ -81,7 +80,6 @@ public class CypherTest { @ClassRule public static DbmsRule db = new ImpermanentDbmsRule() .withSetting(GraphDatabaseSettings.allow_file_urls, true) - .withSetting(GraphDatabaseInternalSettings.enable_experimental_cypher_versions, true) .withSetting( GraphDatabaseSettings.load_csv_file_url_root, new File("src/test/resources").toPath().toAbsolutePath()); diff --git a/core/src/test/java/apoc/export/ImportAndLoadCoreSecurityTest.java b/core/src/test/java/apoc/export/ImportAndLoadCoreSecurityTest.java index 006880daa..a5df03e84 100644 --- a/core/src/test/java/apoc/export/ImportAndLoadCoreSecurityTest.java +++ b/core/src/test/java/apoc/export/ImportAndLoadCoreSecurityTest.java @@ -21,6 +21,7 @@ import static apoc.export.SecurityTestUtil.ALLOWED_EXCEPTIONS; import static apoc.export.SecurityTestUtil.IMPORT_PROCEDURES; import static apoc.export.SecurityTestUtil.LOAD_PROCEDURES; +import static apoc.export.SecurityTestUtil.cypher5OnlyProcedures; import static apoc.export.SecurityTestUtil.setImportFileApocConfigs; import static apoc.util.FileTestUtil.createTempFolder; import static apoc.util.FileUtils.ACCESS_OUTSIDE_DIR_ERROR; @@ -89,7 +90,8 @@ public class ImportAndLoadCoreSecurityTest { private final String fileName; public ImportAndLoadCoreSecurityTest(String method, String methodArguments, String fileName) { - this.apocProcedure = "CALL " + method + methodArguments; + var cypherVersion = cypher5OnlyProcedures.contains(method) ? "CYPHER 5 " : ""; + this.apocProcedure = cypherVersion + "CALL " + method + methodArguments; this.importMethod = method; this.fileName = fileName; } diff --git a/core/src/test/java/apoc/export/SecurityTestUtil.java b/core/src/test/java/apoc/export/SecurityTestUtil.java index 0dc5e9edb..dc79c0bd8 100644 --- a/core/src/test/java/apoc/export/SecurityTestUtil.java +++ b/core/src/test/java/apoc/export/SecurityTestUtil.java @@ -79,6 +79,8 @@ public class SecurityTestUtil { Pair.of("xml", "($fileName, '', {}, false)"), Pair.of("arrow", "($fileName)")); + public static List cypher5OnlyProcedures = List.of("apoc.load.arrow", "apoc.load.jsonParams"); + public static void assertPathTraversalError( GraphDatabaseService db, String query, Map params, Consumer exceptionConsumer) { diff --git a/core/src/test/java/apoc/export/arrow/ArrowTest.java b/core/src/test/java/apoc/export/arrow/ArrowTest.java index 964f4374d..417053abb 100644 --- a/core/src/test/java/apoc/export/arrow/ArrowTest.java +++ b/core/src/test/java/apoc/export/arrow/ArrowTest.java @@ -43,11 +43,15 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.graphdb.Result; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; +/** + * CYPHER 5 only; moved to extended for Cypher 25 + */ public class ArrowTest { private static File directory = new File("target/arrow import"); @@ -60,7 +64,10 @@ public class ArrowTest { public static DbmsRule db = new ImpermanentDbmsRule() .withSetting( GraphDatabaseSettings.load_csv_file_url_root, - directory.toPath().toAbsolutePath()); + directory.toPath().toAbsolutePath()) + .withSetting( + GraphDatabaseInternalSettings.default_cypher_version, + GraphDatabaseInternalSettings.CypherVersion.Cypher5); public static final List> EXPECTED = List.of( new HashMap<>() { diff --git a/core/src/test/java/apoc/export/arrow/ExportArrowSecurityTest.java b/core/src/test/java/apoc/export/arrow/ExportArrowSecurityTest.java index b1457a36f..d0fef4276 100644 --- a/core/src/test/java/apoc/export/arrow/ExportArrowSecurityTest.java +++ b/core/src/test/java/apoc/export/arrow/ExportArrowSecurityTest.java @@ -54,11 +54,15 @@ import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; +/** + * CYPHER 5 only; moved to extended for Cypher 25 + */ @RunWith(Enclosed.class) public class ExportArrowSecurityTest { public static final File directory = new File("target/import"); @@ -84,7 +88,10 @@ public class ExportArrowSecurityTest { public static DbmsRule db = new ImpermanentDbmsRule() .withSetting( GraphDatabaseSettings.load_csv_file_url_root, - directory.toPath().toAbsolutePath()); + directory.toPath().toAbsolutePath()) + .withSetting( + GraphDatabaseInternalSettings.default_cypher_version, + GraphDatabaseInternalSettings.CypherVersion.Cypher5); @BeforeClass public static void setUp() { diff --git a/core/src/test/java/apoc/help/HelpTest.java b/core/src/test/java/apoc/help/HelpTest.java index d7fa445c6..c3146fa9f 100644 --- a/core/src/test/java/apoc/help/HelpTest.java +++ b/core/src/test/java/apoc/help/HelpTest.java @@ -55,8 +55,8 @@ public void teardown() { } @Test - public void info() { - TestUtil.testCall(db, "CALL apoc.help($text)", map("text", "bitwise"), (row) -> { + public void infoCypher5() { + TestUtil.testCall(db, "CYPHER 5 CALL apoc.help($text)", map("text", "bitwise"), (row) -> { assertEquals("function", row.get("type")); assertEquals("apoc.bitwise.op", row.get("name")); assertTrue(((String) row.get("text")).contains("bitwise operation")); @@ -64,23 +64,23 @@ public void info() { }); TestUtil.testCall( db, - "CALL apoc.help($text)", + "CYPHER 5 CALL apoc.help($text)", map("text", "operation+"), (row) -> assertEquals("apoc.bitwise.op", row.get("name"))); - TestUtil.testCall(db, "CALL apoc.help($text)", map("text", "toSet"), (row) -> { + TestUtil.testCall(db, "CYPHER 5 CALL apoc.help($text)", map("text", "toSet"), (row) -> { assertEquals("function", row.get("type")); assertEquals("apoc.coll.toSet", row.get("name")); assertTrue(((String) row.get("text")).contains("unique `LIST`")); assertFalse(((Boolean) row.get("isDeprecated"))); }); - TestUtil.testCall(db, "CALL apoc.help($text)", map("text", "diff.nodes"), (row) -> { + TestUtil.testCall(db, "CYPHER 5 CALL apoc.help($text)", map("text", "diff.nodes"), (row) -> { assertEquals("function", row.get("type")); assertEquals("apoc.diff.nodes", row.get("name")); assertTrue(((String) row.get("text")) .contains("Returns a `MAP` detailing the differences between the two given `NODE` values.")); assertFalse(((Boolean) row.get("isDeprecated"))); }); - TestUtil.testCall(db, "CALL apoc.help($text)", map("text", "apoc.create.uuids"), (row) -> { + TestUtil.testCall(db, "CYPHER 5 CALL apoc.help($text)", map("text", "apoc.create.uuids"), (row) -> { assertEquals("procedure", row.get("type")); assertEquals("apoc.create.uuids", row.get("name")); assertTrue(((String) row.get("text")).contains("Returns a stream of UUIDs.")); @@ -88,6 +88,34 @@ public void info() { }); } + @Test + public void infoCypher25() { + TestUtil.testCall(db, "CYPHER 25 CALL apoc.help($text)", map("text", "bitwise"), (row) -> { + assertEquals("function", row.get("type")); + assertEquals("apoc.bitwise.op", row.get("name")); + assertTrue(((String) row.get("text")).contains("bitwise operation")); + assertFalse(((Boolean) row.get("isDeprecated"))); + }); + TestUtil.testCall( + db, + "CYPHER 25 CALL apoc.help($text)", + map("text", "operation+"), + (row) -> assertEquals("apoc.bitwise.op", row.get("name"))); + TestUtil.testCall(db, "CYPHER 25 CALL apoc.help($text)", map("text", "toSet"), (row) -> { + assertEquals("function", row.get("type")); + assertEquals("apoc.coll.toSet", row.get("name")); + assertTrue(((String) row.get("text")).contains("unique `LIST`")); + assertFalse(((Boolean) row.get("isDeprecated"))); + }); + TestUtil.testCall(db, "CYPHER 25 CALL apoc.help($text)", map("text", "diff.nodes"), (row) -> { + assertEquals("function", row.get("type")); + assertEquals("apoc.diff.nodes", row.get("name")); + assertTrue(((String) row.get("text")) + .contains("Returns a `MAP` detailing the differences between the two given `NODE` values.")); + assertFalse(((Boolean) row.get("isDeprecated"))); + }); + } + @Test public void indicateCore() { TestUtil.testCall( diff --git a/core/src/test/java/apoc/load/LoadJsonTest.java b/core/src/test/java/apoc/load/LoadJsonTest.java index 83d6e51b0..30589295f 100644 --- a/core/src/test/java/apoc/load/LoadJsonTest.java +++ b/core/src/test/java/apoc/load/LoadJsonTest.java @@ -119,7 +119,7 @@ public void testLoadJson() { public void testLoadMultiJsonWithBinary() { testResult( db, - "CALL apoc.load.jsonParams($url, null, null, null, $config)", + "CYPHER 5 CALL apoc.load.jsonParams($url, null, null, null, $config)", map( "url", fileToBinary( @@ -441,7 +441,7 @@ public void testLoadJsonParamsWithAuth() throws Exception { testCall( db, - "call apoc.load.jsonParams($url, $config, $payload)", + "CYPHER 5 CALL apoc.load.jsonParams($url, $config, $payload)", map( "payload", "{\"query\":\"pagecache\",\"version\":\"3.5\"}", @@ -471,7 +471,7 @@ public void testLoadJsonParams() { testCall( db, - "call apoc.load.jsonParams($url, $config, $json)", + "CYPHER 5 CALL apoc.load.jsonParams($url, $config, $json)", map( "json", "{\"query\":\"pagecache\",\"version\":\"3.5\"}", diff --git a/core/src/test/java/apoc/map/MapsTest.java b/core/src/test/java/apoc/map/MapsTest.java index f265b6862..b44eb1f3d 100644 --- a/core/src/test/java/apoc/map/MapsTest.java +++ b/core/src/test/java/apoc/map/MapsTest.java @@ -217,7 +217,7 @@ public void testSetKey() { @Test public void testSetEntry() { - TestUtil.testCall(db, "RETURN apoc.map.setEntry({a:1},'a',2) AS value", (r) -> { + TestUtil.testCall(db, "CYPHER 5 RETURN apoc.map.setEntry({a:1},'a',2) AS value", (r) -> { assertEquals(map("a", 2L), r.get("value")); }); } diff --git a/core/src/test/java/apoc/meta/MetaTest.java b/core/src/test/java/apoc/meta/MetaTest.java index 588a8bbaa..5913870e9 100644 --- a/core/src/test/java/apoc/meta/MetaTest.java +++ b/core/src/test/java/apoc/meta/MetaTest.java @@ -1569,7 +1569,7 @@ public void testNodeTypePropertiesEquivalenceAdvanced() { // Missing all properties to make everything non-mandatory. db.executeTransactionally("CREATE (:Foo { z: 1 });"); assertTrue(testDBCallEquivalence( - db, "CALL apoc.meta.nodeTypeProperties()", "CALL db.schema.nodeTypeProperties()")); + db, "CALL apoc.meta.nodeTypeProperties()", "CYPHER 5 CALL db.schema.nodeTypeProperties()")); } @Test @@ -1578,8 +1578,8 @@ public void testRelTypePropertiesEquivalenceAdvanced() { "CREATE (:Foo)-[:REL { l: 1, s: 'foo', d: datetime(), ll: ['a', 'b'], dl: [2.0, 3.0] }]->();"); // Missing all properties to make everything non-mandatory. db.executeTransactionally("CREATE (:Foo)-[:REL { z: 1 }]->();"); - assertTrue( - testDBCallEquivalence(db, "CALL apoc.meta.relTypeProperties()", "CALL db.schema.relTypeProperties()")); + assertTrue(testDBCallEquivalence( + db, "CALL apoc.meta.relTypeProperties()", "CYPHER 5 CALL db.schema.relTypeProperties()")); } @Test @@ -1601,7 +1601,7 @@ public void testNodeTypePropertiesEquivalenceTypeMapping() { db.executeTransactionally(q); assertTrue(testDBCallEquivalence( - db, "CALL apoc.meta.nodeTypeProperties()", "CALL db.schema.nodeTypeProperties()")); + db, "CALL apoc.meta.nodeTypeProperties()", "CYPHER 5 CALL db.schema.nodeTypeProperties()")); } @Test @@ -1622,8 +1622,8 @@ public void testRelTypePropertiesEquivalenceTypeMapping() { + "CREATE (b:Test)-[:REL{ randomProp: 'this property is here to make everything mandatory = false'}]->(b);"; db.executeTransactionally(q); - assertTrue( - testDBCallEquivalence(db, "CALL apoc.meta.relTypeProperties()", "CALL db.schema.relTypeProperties()")); + assertTrue(testDBCallEquivalence( + db, "CALL apoc.meta.relTypeProperties()", "CYPHER 5 CALL db.schema.relTypeProperties()")); } @Test diff --git a/core/src/test/java/apoc/schema/SchemasTest.java b/core/src/test/java/apoc/schema/SchemasTest.java index 8f5de4ece..fcbefc2ac 100644 --- a/core/src/test/java/apoc/schema/SchemasTest.java +++ b/core/src/test/java/apoc/schema/SchemasTest.java @@ -47,7 +47,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.graphdb.Label; import org.neo4j.graphdb.QueryExecutionException; @@ -70,55 +69,7 @@ public class SchemasTest { @Rule public DbmsRule db = new ImpermanentDbmsRule() - .withSetting(GraphDatabaseSettings.procedure_unrestricted, Collections.singletonList("apoc.*")) - .withSetting(GraphDatabaseInternalSettings.enable_experimental_cypher_versions, true); - - private static void accept(Result result) { - Map r = result.next(); - - assertEquals(":Foo(bar)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals("Foo", r.get("label")); - assertEquals("RANGE", r.get("type")); - assertEquals("bar", ((List) r.get("properties")).get(0)); - assertEquals("NO FAILURE", r.get("failure")); - assertEquals(100d, r.get("populationProgress")); - assertEquals(1d, r.get("valuesSelectivity")); - Assertions.assertThat(r.get("userDescription").toString()) - .contains("name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); - - assertTrue(!result.hasNext()); - } - - private static void accept2(Result result) { - Map r = result.next(); - - assertEquals(":Foo(bar)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals("Foo", r.get("label")); - assertEquals("RANGE", r.get("type")); - assertEquals("bar", ((List) r.get("properties")).get(0)); - assertEquals("NO FAILURE", r.get("failure")); - assertEquals(100d, r.get("populationProgress")); - assertEquals(1d, r.get("valuesSelectivity")); - Assertions.assertThat(r.get("userDescription").toString()) - .contains("name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); - - r = result.next(); - - assertEquals(":Person(name)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals("Person", r.get("label")); - assertEquals("TEXT", r.get("type")); - assertEquals("name", ((List) r.get("properties")).get(0)); - assertEquals("NO FAILURE", r.get("failure")); - assertEquals(100d, r.get("populationProgress")); - assertEquals(1d, r.get("valuesSelectivity")); - Assertions.assertThat(r.get("userDescription").toString()) - .contains("name='index3', type='TEXT', schema=(:Person {name}), indexProvider='text-2.0' )"); - - assertTrue(!result.hasNext()); - } + .withSetting(GraphDatabaseSettings.procedure_unrestricted, Collections.singletonList("apoc.*")); @Before public void setUp() { @@ -366,36 +317,54 @@ public void testKeepSchema() { public void testIndexes() { db.executeTransactionally("CREATE RANGE INDEX index1 FOR (n:Foo) ON (n.bar)"); awaitIndexesOnline(); - testResult(db, "CALL apoc.schema.nodes()", (result) -> { - // Get the index info - Map r = result.next(); - assertEquals(":Foo(bar)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals("Foo", r.get("label")); - assertEquals("RANGE", r.get("type")); - assertEquals("bar", ((List) r.get("properties")).get(0)); - assertEquals("NO FAILURE", r.get("failure")); - assertEquals(100d, r.get("populationProgress")); - assertEquals(1d, r.get("valuesSelectivity")); - Assertions.assertThat(r.get("userDescription").toString()) - .contains("name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult(db, preparser + "CALL apoc.schema.nodes()", (result) -> { + // Get the index info + Map r = result.next(); - assertFalse(result.hasNext()); - }); + assertEquals("ONLINE", r.get("status")); + assertEquals("Foo", r.get("label")); + assertEquals("RANGE", r.get("type")); + assertEquals("bar", ((List) r.get("properties")).get(0)); + assertEquals("NO FAILURE", r.get("failure")); + assertEquals(100d, r.get("populationProgress")); + assertEquals(1d, r.get("valuesSelectivity")); + Assertions.assertThat(r.get("userDescription").toString()) + .contains("name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); + + if (cypherVersion.equals("5")) { + assertEquals(":Foo(bar)", r.get("name")); + } else { + assertEquals("index1", r.get("name")); + } + + assertFalse(result.hasNext()); + }); + } } @Test public void testRelIndex() { db.executeTransactionally("CREATE INDEX FOR ()-[r:KNOWS]-() ON (r.id, r.since)"); awaitIndexesOnline(); - testCall(db, "CALL apoc.schema.relationships()", row -> { - assertEquals(":KNOWS(id,since)", row.get("name")); - assertEquals("ONLINE", row.get("status")); - assertEquals("KNOWS", row.get("relationshipType")); - assertEquals("RANGE", row.get("type")); - assertEquals(List.of("id", "since"), row.get("properties")); - }); + + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testCall(db, preparser + "CALL apoc.schema.relationships()", row -> { + assertEquals("ONLINE", row.get("status")); + assertEquals("KNOWS", row.get("relationshipType")); + assertEquals("RANGE", row.get("type")); + assertEquals(List.of("id", "since"), row.get("properties")); + + if (cypherVersion.equals("5")) { + assertEquals(":KNOWS(id,since)", row.get("name")); + } else { + assertTrue(row.get("name").toString().startsWith("index_")); + } + }); + } } @Test @@ -436,34 +405,47 @@ public void testUniquenessConstraintOnNode() { db.executeTransactionally("CREATE CONSTRAINT FOR (bar:Bar) REQUIRE bar.foo IS UNIQUE"); awaitIndexesOnline(); - testResult(db, CALL_SCHEMA_NODES_ORDERED, (result) -> { - Map r = result.next(); - - assertionsBarFooUniqueCons(r); - - assertEquals("RANGE", r.get("type")); - assertEquals("ONLINE", r.get("status")); - final String expectedUserDescBarIdx = - "name='constraint_4791de3e', type='RANGE', schema=(:Bar {foo}), indexProvider='range-1.0', owningConstraint"; - Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarIdx); - r = result.next(); - - assertionsBarFooUniqueCons(r); - - assertEquals("UNIQUENESS", r.get("type")); - assertEquals("", r.get("status")); - final String expectedUserDescBarCons = - "name='constraint_4791de3e', type='UNIQUENESS', schema=(:Bar {foo}), ownedIndex=3"; - Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); - - assertFalse(result.hasNext()); - }); - } + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult(db, preparser + CALL_SCHEMA_NODES_ORDERED, (result) -> { + Map r = result.next(); - private static void assertionsBarFooUniqueCons(Map r) { - assertEquals(":Bar(foo)", r.get("name")); - assertEquals("Bar", r.get("label")); - assertEquals(List.of("foo"), r.get("properties")); + assertEquals("Bar", r.get("label")); + assertEquals(List.of("foo"), r.get("properties")); + assertEquals("RANGE", r.get("type")); + assertEquals("ONLINE", r.get("status")); + final String expectedUserDescBarIdx = + "name='constraint_4791de3e', type='RANGE', schema=(:Bar {foo}), indexProvider='range-1.0', owningConstraint"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarIdx); + + if (cypherVersion.equals("5")) { + assertEquals(":Bar(foo)", r.get("name")); + } else { + assertTrue(r.get("name").toString().startsWith("constraint_")); + } + + r = result.next(); + + assertEquals("Bar", r.get("label")); + assertEquals(List.of("foo"), r.get("properties")); + assertEquals("UNIQUENESS", r.get("type")); + assertEquals("", r.get("status")); + if (cypherVersion.equals("5")) { + assertEquals(":Bar(foo)", r.get("name")); + final String expectedUserDescBarCons = + "name='constraint_4791de3e', type='UNIQUENESS', schema=(:Bar {foo}), ownedIndex=3"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); + + } else { + assertTrue(r.get("name").toString().startsWith("constraint_")); + final String expectedUserDescBarCons = + "name='constraint_4791de3e', type='NODE PROPERTY UNIQUENESS', schema=(:Bar {foo}), ownedIndex=3"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); + } + + assertFalse(result.hasNext()); + }); + } } @Test @@ -472,43 +454,59 @@ public void testIndexAndUniquenessConstraintOnNode() { db.executeTransactionally("CREATE CONSTRAINT bar_unique FOR (bar:Bar) REQUIRE bar.bar IS UNIQUE"); awaitIndexesOnline(); - testResult(db, CALL_SCHEMA_NODES_ORDERED, (result) -> { - Map r = result.next(); - - assertionsBarUniqueCons(r); - assertEquals("RANGE", r.get("type")); - assertEquals("ONLINE", r.get("status")); - final String expectedUserDescBarIdx = - "name='bar_unique', type='RANGE', schema=(:Bar {bar}), indexProvider='range-1.0', owningConstraint"; - Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarIdx); - - r = result.next(); - - assertionsBarUniqueCons(r); - assertEquals("UNIQUENESS", r.get("type")); - assertEquals("", r.get("status")); - final String expectedUserDescBarCons = - "name='bar_unique', type='UNIQUENESS', schema=(:Bar {bar}), ownedIndex"; - Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); - - r = result.next(); - assertEquals("Foo", r.get("label")); - assertEquals("RANGE", r.get("type")); - assertEquals("foo", ((List) r.get("properties")).get(0)); - assertEquals("ONLINE", r.get("status")); - assertEquals(":Foo(foo)", r.get("name")); - final String expectedUserDescFoo = - "name='foo_idx', type='RANGE', schema=(:Foo {foo}), indexProvider='range-1.0' )"; - Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescFoo); - - assertFalse(result.hasNext()); - }); - } + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult(db, preparser + CALL_SCHEMA_NODES_ORDERED, (result) -> { + Map r = result.next(); - private static void assertionsBarUniqueCons(Map r) { - assertEquals("Bar", r.get("label")); - assertEquals(":Bar(bar)", r.get("name")); - assertEquals(List.of("bar"), r.get("properties")); + assertEquals("Bar", r.get("label")); + assertEquals(List.of("bar"), r.get("properties")); + assertEquals("RANGE", r.get("type")); + assertEquals("ONLINE", r.get("status")); + + if (cypherVersion.equals("5")) { + assertEquals(":Bar(bar)", r.get("name")); + } else { + assertEquals("bar_unique", r.get("name")); + } + + r = result.next(); + + assertEquals("Bar", r.get("label")); + assertEquals(List.of("bar"), r.get("properties")); + assertEquals("UNIQUENESS", r.get("type")); + assertEquals("", r.get("status")); + + if (cypherVersion.equals("5")) { + assertEquals(":Bar(bar)", r.get("name")); + final String expectedUserDescBarCons = + "name='bar_unique', type='UNIQUENESS', schema=(:Bar {bar}), ownedIndex"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); + } else { + assertEquals("bar_unique", r.get("name")); + final String expectedUserDescBarCons = + "name='bar_unique', type='NODE PROPERTY UNIQUENESS', schema=(:Bar {bar}), ownedIndex"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); + } + + r = result.next(); + assertEquals("Foo", r.get("label")); + assertEquals("RANGE", r.get("type")); + assertEquals("foo", ((List) r.get("properties")).get(0)); + assertEquals("ONLINE", r.get("status")); + final String expectedUserDescFoo = + "name='foo_idx', type='RANGE', schema=(:Foo {foo}), indexProvider='range-1.0' )"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescFoo); + + if (cypherVersion.equals("5")) { + assertEquals(":Foo(foo)", r.get("name")); + } else { + assertEquals("foo_idx", r.get("name")); + } + + assertFalse(result.hasNext()); + }); + } } @Test @@ -765,10 +763,34 @@ public void testIndexesOneLabel() { db.executeTransactionally("CREATE TEXT INDEX index3 FOR (n:Person) ON (n.name)"); db.executeTransactionally("CREATE TEXT INDEX index4 FOR (n:Movie) ON (n.title)"); awaitIndexesOnline(); - testResult( - db, - "CALL apoc.schema.nodes({labels:['Foo']})", // Get the index info - SchemasTest::accept); + + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult( + db, + preparser + "CALL apoc.schema.nodes({labels:['Foo']})", // Get the index info + (result -> { + Map r = result.next(); + + assertEquals("ONLINE", r.get("status")); + assertEquals("Foo", r.get("label")); + assertEquals("RANGE", r.get("type")); + assertEquals("bar", ((List) r.get("properties")).get(0)); + assertEquals("NO FAILURE", r.get("failure")); + assertEquals(100d, r.get("populationProgress")); + assertEquals(1d, r.get("valuesSelectivity")); + Assertions.assertThat(r.get("userDescription").toString()) + .contains( + "name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); + + if (cypherVersion.equals("5")) { + assertEquals(":Foo(bar)", r.get("name")); + } else { + assertEquals("index1", r.get("name")); + } + assertTrue(!result.hasNext()); + })); + } } private void awaitIndexesOnline() { @@ -785,10 +807,54 @@ public void testIndexesMoreLabels() { db.executeTransactionally("CREATE TEXT INDEX index3 FOR (n:Person) ON (n.name)"); db.executeTransactionally("CREATE TEXT INDEX index4 FOR (n:Movie) ON (n.title)"); awaitIndexesOnline(); - testResult( - db, - "CALL apoc.schema.nodes({labels:['Foo', 'Person']})", // Get the index info - SchemasTest::accept2); + + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult( + db, + preparser + "CALL apoc.schema.nodes({labels:['Foo', 'Person']})", // Get the index info + (result) -> { + Map r = result.next(); + + assertEquals("ONLINE", r.get("status")); + assertEquals("Foo", r.get("label")); + assertEquals("RANGE", r.get("type")); + assertEquals("bar", ((List) r.get("properties")).get(0)); + assertEquals("NO FAILURE", r.get("failure")); + assertEquals(100d, r.get("populationProgress")); + assertEquals(1d, r.get("valuesSelectivity")); + Assertions.assertThat(r.get("userDescription").toString()) + .contains( + "name='index1', type='RANGE', schema=(:Foo {bar}), indexProvider='range-1.0' )"); + + if (cypherVersion.equals("5")) { + assertEquals(":Foo(bar)", r.get("name")); + } else { + assertEquals("index1", r.get("name")); + } + + r = result.next(); + + assertEquals("ONLINE", r.get("status")); + assertEquals("Person", r.get("label")); + assertEquals("TEXT", r.get("type")); + assertEquals("name", ((List) r.get("properties")).get(0)); + assertEquals("NO FAILURE", r.get("failure")); + assertEquals(100d, r.get("populationProgress")); + assertEquals(1d, r.get("valuesSelectivity")); + Assertions.assertThat(r.get("userDescription").toString()) + .contains( + "name='index3', type='TEXT', schema=(:Person {name}), indexProvider='text-2.0' )"); + + if (cypherVersion.equals("5")) { + assertEquals(":Person(name)", r.get("name")); + } else { + assertEquals("index3", r.get("name")); + } + + assertTrue(!result.hasNext()); + }); + } } @Test @@ -847,21 +913,35 @@ public void testUniqueRelationshipConstraint() { db.executeTransactionally("CREATE CONSTRAINT FOR ()-[since:SINCE]-() REQUIRE since.year IS UNIQUE"); awaitIndexesOnline(); - testResult(db, "CALL apoc.schema.relationships({})", result -> { - Map r = result.next(); - assertEquals("CONSTRAINT FOR ()-[since:SINCE]-() REQUIRE since.year IS UNIQUE", r.get("name")); - assertEquals("RELATIONSHIP_UNIQUENESS", r.get("type")); - assertEquals("SINCE", r.get("relationshipType")); - assertEquals("year", ((List) r.get("properties")).get(0)); - - r = result.next(); - - assertEquals(":SINCE(year)", r.get("name")); - assertEquals("RANGE", r.get("type")); - assertEquals("SINCE", r.get("relationshipType")); - assertEquals("year", ((List) r.get("properties")).get(0)); - assertTrue(!result.hasNext()); - }); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult(db, preparser + "CALL apoc.schema.relationships({})", result -> { + Map r = result.next(); + assertEquals("RELATIONSHIP_UNIQUENESS", r.get("type")); + assertEquals("SINCE", r.get("relationshipType")); + assertEquals("year", ((List) r.get("properties")).get(0)); + + if (cypherVersion.equals("5")) { + assertEquals("CONSTRAINT FOR ()-[since:SINCE]-() REQUIRE since.year IS UNIQUE", r.get("name")); + } else { + assertTrue(r.get("name").toString().startsWith("constraint_")); + } + + r = result.next(); + + assertEquals("RANGE", r.get("type")); + assertEquals("SINCE", r.get("relationshipType")); + assertEquals("year", ((List) r.get("properties")).get(0)); + + if (cypherVersion.equals("5")) { + assertEquals(":SINCE(year)", r.get("name")); + } else { + assertTrue(r.get("name").toString().startsWith("constraint_")); + } + + assertTrue(!result.hasNext()); + }); + } } @Test @@ -982,21 +1062,26 @@ public void testMultipleUniqueRelationshipConstraints() { relConstraints.add( new IndexConstraintRelationshipInfo(":KNOW(how)", "RANGE", List.of("how"), "ONLINE", "KNOW")); - testResult(db, "CALL apoc.schema.relationships", result -> { - while (result.hasNext()) { - Map r = result.next(); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testResult(db, "CALL apoc.schema.relationships", result -> { + while (result.hasNext()) { + Map r = result.next(); - assertEquals( - 1, - relConstraints.stream() - .filter(c -> c.name.equals(r.get("name")) - && c.properties.containsAll((List) r.get("properties")) - && c.type.equals(r.get("type")) - && c.relationshipType.equals(r.get("relationshipType"))) - .toList() - .size()); - } - }); + assertEquals( + 1, + relConstraints.stream() + .filter(c -> c.properties.containsAll((List) r.get("properties")) + && c.type.equals(r.get("type")) + && c.relationshipType.equals(r.get("relationshipType")) + && (c.name.equals(r.get("name")) + || r.get("name").toString().startsWith("index_") + || r.get("name").toString().startsWith("constraint_"))) + .toList() + .size()); + } + }); + } } @Test @@ -1005,27 +1090,40 @@ public void testLookupIndexes() { db.executeTransactionally("CREATE LOOKUP INDEX rel_type_lookup_index FOR ()-[r]-() ON EACH type(r)"); awaitIndexesOnline(); - testCall(db, "CALL apoc.schema.nodes()", (row) -> { - assertEquals(":" + TOKEN_LABEL + "()", row.get("name")); - assertEquals("ONLINE", row.get("status")); - assertEquals(TOKEN_LABEL, row.get("label")); - assertEquals("LOOKUP", row.get("type")); - assertTrue(((List) row.get("properties")).isEmpty()); - assertEquals("NO FAILURE", row.get("failure")); - assertEquals(100d, row.get("populationProgress")); - assertEquals(1d, row.get("valuesSelectivity")); - final String expectedUserDesc = - "name='node_label_lookup_index', type='LOOKUP', schema=(:), indexProvider='token-lookup-1.0'"; - Assertions.assertThat(row.get("userDescription").toString()).contains(expectedUserDesc); - }); - - testCall(db, "CALL apoc.schema.relationships()", (row) -> { - assertEquals(":" + TOKEN_REL_TYPE + "()", row.get("name")); - assertEquals("ONLINE", row.get("status")); - assertEquals("LOOKUP", row.get("type")); - assertEquals(TOKEN_REL_TYPE, row.get("relationshipType")); - assertTrue(((List) row.get("properties")).isEmpty()); - }); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testCall(db, preparser + "CALL apoc.schema.nodes()", (row) -> { + assertEquals("ONLINE", row.get("status")); + assertEquals(TOKEN_LABEL, row.get("label")); + assertEquals("LOOKUP", row.get("type")); + assertTrue(((List) row.get("properties")).isEmpty()); + assertEquals("NO FAILURE", row.get("failure")); + assertEquals(100d, row.get("populationProgress")); + assertEquals(1d, row.get("valuesSelectivity")); + final String expectedUserDesc = + "name='node_label_lookup_index', type='LOOKUP', schema=(:), indexProvider='token-lookup-1.0'"; + Assertions.assertThat(row.get("userDescription").toString()).contains(expectedUserDesc); + + if (cypherVersion.equals("5")) { + assertEquals(":" + TOKEN_LABEL + "()", row.get("name")); + } else { + assertEquals("node_label_lookup_index", row.get("name")); + } + }); + + testCall(db, preparser + "CALL apoc.schema.relationships()", (row) -> { + assertEquals("ONLINE", row.get("status")); + assertEquals("LOOKUP", row.get("type")); + assertEquals(TOKEN_REL_TYPE, row.get("relationshipType")); + assertTrue(((List) row.get("properties")).isEmpty()); + + if (cypherVersion.equals("5")) { + assertEquals(":" + TOKEN_REL_TYPE + "()", row.get("name")); + } else { + assertEquals("rel_type_lookup_index", row.get("name")); + } + }); + } } private void dropSchema() { @@ -1046,33 +1144,47 @@ public void testIndexesWithMultipleLabelsAndRelTypes() { "CREATE FULLTEXT INDEX fullIdxRel FOR ()-[r:TYPE_1|TYPE_2]->() ON EACH [r.alpha, r.beta]"); awaitIndexesOnline(); - testCall(db, "CALL apoc.schema.nodes()", (r) -> { - assertEquals(":[Blah, Moon],(weightProp,anotherProp)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals(List.of("Blah", "Moon"), r.get("label")); - assertEquals("FULLTEXT", r.get("type")); - assertEquals(List.of("weightProp", "anotherProp"), r.get("properties")); - assertEquals("NO FAILURE", r.get("failure")); - assertEquals(100d, r.get("populationProgress")); - assertEquals(1d, r.get("valuesSelectivity")); - final long indexId = db.executeTransactionally( - "SHOW INDEXES YIELD id, name WHERE name = $indexName RETURN id", - Map.of("indexName", idxName), - res -> res.columnAs("id").next()); - String expectedIndexDescription = String.format( - "Index( id=%s, name='%s', type='FULLTEXT', " - + "schema=(:Blah:Moon {weightProp, anotherProp}), indexProvider='fulltext-1.0' )", - indexId, idxName); - assertEquals(expectedIndexDescription, r.get("userDescription")); - }); - - testCall(db, "CALL apoc.schema.relationships()", (r) -> { - assertEquals(":[TYPE_1, TYPE_2],(alpha,beta)", r.get("name")); - assertEquals("ONLINE", r.get("status")); - assertEquals(List.of("TYPE_1", "TYPE_2"), r.get("relationshipType")); - assertEquals(List.of("alpha", "beta"), r.get("properties")); - assertEquals("FULLTEXT", r.get("type")); - }); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testCall(db, preparser + "CALL apoc.schema.nodes()", (r) -> { + assertEquals("ONLINE", r.get("status")); + assertEquals(List.of("Blah", "Moon"), r.get("label")); + assertEquals("FULLTEXT", r.get("type")); + assertEquals(List.of("weightProp", "anotherProp"), r.get("properties")); + assertEquals("NO FAILURE", r.get("failure")); + assertEquals(100d, r.get("populationProgress")); + assertEquals(1d, r.get("valuesSelectivity")); + final long indexId = db.executeTransactionally( + "SHOW INDEXES YIELD id, name WHERE name = $indexName RETURN id", + Map.of("indexName", idxName), + res -> res.columnAs("id").next()); + + String expectedIndexDescription = String.format( + "Index( id=%s, name='%s', type='FULLTEXT', " + + "schema=(:Blah:Moon {weightProp, anotherProp}), indexProvider='fulltext-1.0' )", + indexId, idxName); + assertEquals(expectedIndexDescription, r.get("userDescription")); + + if (cypherVersion.equals("5")) { + assertEquals(":[Blah, Moon],(weightProp,anotherProp)", r.get("name")); + } else { + assertEquals("fullIdxNode", r.get("name")); + } + }); + + testCall(db, preparser + "CALL apoc.schema.relationships()", (r) -> { + assertEquals("ONLINE", r.get("status")); + assertEquals(List.of("TYPE_1", "TYPE_2"), r.get("relationshipType")); + assertEquals(List.of("alpha", "beta"), r.get("properties")); + assertEquals("FULLTEXT", r.get("type")); + + if (cypherVersion.equals("5")) { + assertEquals(":[TYPE_1, TYPE_2],(alpha,beta)", r.get("name")); + } else { + assertEquals("fullIdxRel", r.get("name")); + } + }); + } } @Test @@ -1154,22 +1266,31 @@ public void testSchemaNodesWithFailedIndex() { db.executeTransactionally("CREATE INDEX failedIdx FOR (n:LabelTest) ON (n.prop)"); Assert.assertThrows(IllegalStateException.class, this::awaitIndexesOnline); - testCall(db, "SHOW INDEXES YIELD name, state WHERE name = 'failedIdx'", (r) -> { - assertEquals("FAILED", r.get("state")); - }); - - // then - testCall(db, "CALL apoc.schema.nodes", r -> { - String actualFailure = (String) r.get("failure"); - String expectedFailure = - "Property value is too large to index, please see index documentation for limitations."; - assertThat(actualFailure, containsString(expectedFailure)); - assertEquals(":LabelTest(prop)", r.get("name")); - assertEquals("FAILED", r.get("status")); - assertEquals("LabelTest", r.get("label")); - assertEquals("RANGE", r.get("type")); - assertEquals(List.of("prop"), r.get("properties")); - }); + testCall( + db, + "SHOW INDEXES YIELD name, state WHERE name = 'failedIdx'", + (r) -> assertEquals("FAILED", r.get("state"))); + + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testCall(db, preparser + "CALL apoc.schema.nodes", r -> { + String actualFailure = (String) r.get("failure"); + String expectedFailure = + "Property value is too large to index, please see index documentation for limitations."; + assertThat(actualFailure, containsString(expectedFailure)); + assertEquals("FAILED", r.get("status")); + assertEquals("LabelTest", r.get("label")); + assertEquals("RANGE", r.get("type")); + assertEquals(List.of("prop"), r.get("properties")); + + // Cypher 25 updated to using the real name of the index, and not creating one. + if (cypherVersion.equals("5")) { + assertEquals(":LabelTest(prop)", r.get("name")); + } else { + assertEquals("failedIdx", r.get("name")); + } + }); + } } @Test @@ -1189,13 +1310,20 @@ public void testSchemaRelationshipsWithFailedIndex() { assertEquals("FAILED", r.get("state")); }); - // then - testCall(db, "CALL apoc.schema.relationships", r -> { - assertEquals(":REL_TEST(prop)", r.get("name")); - assertEquals("FAILED", r.get("status")); - assertEquals("REL_TEST", r.get("relationshipType")); - assertEquals("RANGE", r.get("type")); - assertEquals(List.of("prop"), r.get("properties")); - }); + for (String cypherVersion : Util.getCypherVersions()) { + var preparser = "CYPHER " + cypherVersion + " "; + testCall(db, preparser + "CALL apoc.schema.relationships", r -> { + assertEquals("FAILED", r.get("status")); + assertEquals("REL_TEST", r.get("relationshipType")); + assertEquals("RANGE", r.get("type")); + assertEquals(List.of("prop"), r.get("properties")); + + if (cypherVersion.equals("5")) { + assertEquals(":REL_TEST(prop)", r.get("name")); + } else { + assertEquals("failedIdx", r.get("name")); + } + }); + } } } diff --git a/core/src/test/java/apoc/text/StringsTest.java b/core/src/test/java/apoc/text/StringsTest.java index f1dfaa4ff..c5e207abb 100644 --- a/core/src/test/java/apoc/text/StringsTest.java +++ b/core/src/test/java/apoc/text/StringsTest.java @@ -157,7 +157,7 @@ public void testReplace() { testCall( db, - "RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", + "CYPHER 5 RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", map("text", text, "regex", regex, "replacement", replacement), row -> assertEquals(expected, row.get("value"))); } @@ -169,17 +169,17 @@ public void testReplaceAllWithNull() { String replacement = ""; testCall( db, - "RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", + "CYPHER 5 RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", map("text", null, "regex", regex, "replacement", replacement), row -> assertEquals(null, row.get("value"))); testCall( db, - "RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", + "CYPHER 5 RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", map("text", text, "regex", null, "replacement", replacement), row -> assertEquals(null, row.get("value"))); testCall( db, - "RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", + "CYPHER 5 RETURN apoc.text.regreplace($text,$regex,$replacement) AS value", map("text", text, "regex", regex, "replacement", null), row -> assertEquals(null, row.get("value"))); } @@ -397,7 +397,7 @@ public void testFuzzyMatchIntegration() { public void testDocReplace() { testCall( db, - "RETURN apoc.text.regreplace('Hello World!', '[^a-zA-Z]', '') AS value", + "CYPHER 5 RETURN apoc.text.regreplace('Hello World!', '[^a-zA-Z]', '') AS value", row -> assertEquals("HelloWorld", row.get("value"))); } diff --git a/core/src/test/java/apoc/trigger/TriggerDisabledTest.java b/core/src/test/java/apoc/trigger/TriggerDisabledTest.java index ea6953c87..229d93965 100644 --- a/core/src/test/java/apoc/trigger/TriggerDisabledTest.java +++ b/core/src/test/java/apoc/trigger/TriggerDisabledTest.java @@ -29,13 +29,13 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.graphdb.Result; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; /** - * @author alexiudice - * @since 14.07.18 + * CYPHER 5 only; moved to extended for Cypher 25 *

* Tests for fix of #845. *

@@ -47,7 +47,10 @@ public class TriggerDisabledTest { @ClassRule - public static DbmsRule db = new ImpermanentDbmsRule(); + public static DbmsRule db = new ImpermanentDbmsRule() + .withSetting( + GraphDatabaseInternalSettings.default_cypher_version, + GraphDatabaseInternalSettings.CypherVersion.Cypher5); @BeforeClass public static void setUp() { diff --git a/core/src/test/java/apoc/trigger/TriggerTest.java b/core/src/test/java/apoc/trigger/TriggerTest.java index 1dc1109d6..7695f8190 100644 --- a/core/src/test/java/apoc/trigger/TriggerTest.java +++ b/core/src/test/java/apoc/trigger/TriggerTest.java @@ -38,6 +38,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ProvideSystemProperty; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.graphdb.Relationship; @@ -49,8 +50,7 @@ import org.neo4j.test.rule.ImpermanentDbmsRule; /** - * @author mh - * @since 20.09.16 + * CYPHER 5 only; moved to extended for Cypher 25 */ public class TriggerTest { @@ -60,7 +60,11 @@ public class TriggerTest { new ProvideSystemProperty(APOC_TRIGGER_ENABLED, String.valueOf(true)); @Rule - public DbmsRule db = new ImpermanentDbmsRule().withSetting(procedure_unrestricted, List.of("apoc*")); + public DbmsRule db = new ImpermanentDbmsRule() + .withSetting(procedure_unrestricted, List.of("apoc*")) + .withSetting( + GraphDatabaseInternalSettings.default_cypher_version, + GraphDatabaseInternalSettings.CypherVersion.Cypher5); private long start; diff --git a/core/src/test/java/apoc/warmup/WarmupTest.java b/core/src/test/java/apoc/warmup/WarmupTest.java index 53f2e488f..880bf28b2 100644 --- a/core/src/test/java/apoc/warmup/WarmupTest.java +++ b/core/src/test/java/apoc/warmup/WarmupTest.java @@ -29,18 +29,21 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; /** - * @author Sascha Peukert - * @since 06.05.16 + * CYPHER 5 only; moved to extended for Cypher 25 */ public class WarmupTest { @Rule - public DbmsRule db = new ImpermanentDbmsRule(); + public DbmsRule db = new ImpermanentDbmsRule() + .withSetting( + GraphDatabaseInternalSettings.default_cypher_version, + GraphDatabaseInternalSettings.CypherVersion.Cypher5); @Before public void setUp() { diff --git a/it/src/test/java/apoc/it/core/ApocVersionsTest.java b/it/src/test/java/apoc/it/core/ApocVersionsTest.java index ef11c8693..c9de6a339 100644 --- a/it/src/test/java/apoc/it/core/ApocVersionsTest.java +++ b/it/src/test/java/apoc/it/core/ApocVersionsTest.java @@ -101,7 +101,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.configuration.GraphDatabaseSettings; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; @@ -113,8 +112,7 @@ public class ApocVersionsTest { @Rule public DbmsRule db = new ImpermanentDbmsRule() - .withSetting(GraphDatabaseSettings.procedure_unrestricted, singletonList("apoc.*")) - .withSetting(GraphDatabaseInternalSettings.enable_experimental_cypher_versions, true); + .withSetting(GraphDatabaseSettings.procedure_unrestricted, singletonList("apoc.*")); @Before public void setUp() { diff --git a/test-utils/src/main/java/org/neo4j/test/rule/DbmsRule.java b/test-utils/src/main/java/org/neo4j/test/rule/DbmsRule.java index 11a65175b..9aa14e3aa 100644 --- a/test-utils/src/main/java/org/neo4j/test/rule/DbmsRule.java +++ b/test-utils/src/main/java/org/neo4j/test/rule/DbmsRule.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.neo4j.common.DependencyResolver; +import org.neo4j.configuration.GraphDatabaseInternalSettings; import org.neo4j.dbms.api.DatabaseManagementService; import org.neo4j.dbms.api.Neo4jDatabaseManagementServiceBuilder; import org.neo4j.dbms.systemgraph.TopologyGraphDbmsModel.HostedOnMode; @@ -153,6 +154,19 @@ protected void after() { private void create() { databaseBuilder = newFactory(); + + // Allow experimental versions of Cypher and set Cypher Default Version + globalConfig.put(GraphDatabaseInternalSettings.enable_experimental_cypher_versions, true); + + // A test may set this for the entire file itself, so we shouldn't override that + if (!globalConfig.containsKey(GraphDatabaseInternalSettings.default_cypher_version)) { + String cypherVersionEnv = System.getenv() + .getOrDefault("CYPHER_VERSION", GraphDatabaseInternalSettings.CypherVersion.Cypher5.name()); + GraphDatabaseInternalSettings.CypherVersion cypherVersion = + GraphDatabaseInternalSettings.CypherVersion.valueOf(cypherVersionEnv); + globalConfig.put(GraphDatabaseInternalSettings.default_cypher_version, cypherVersion); + } + databaseBuilder.setConfig(globalConfig); }