Skip to content

Commit

Permalink
Add tests to update procs/funcs
Browse files Browse the repository at this point in the history
  • Loading branch information
gem-neo4j committed Feb 5, 2025
1 parent a1736e0 commit 4449e2d
Show file tree
Hide file tree
Showing 16 changed files with 590 additions and 78 deletions.
11 changes: 10 additions & 1 deletion common/src/main/java/apoc/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ public static String getCypherVersionString(ProcedureCallContext procedureCallCo
}

private static final Pattern CYPHER_VERSION_PATTERN =
Pattern.compile("^(CYPHER)(?:\\s+(\\d+))?", Pattern.CASE_INSENSITIVE);
Pattern.compile("^\\s*\\b(CYPHER)(?:\\s+(\\d+))?", Pattern.CASE_INSENSITIVE);
public static final Pattern PLANNER_PATTERN =
Pattern.compile("\\bplanner\\s*=\\s*[^\\s]*", Pattern.CASE_INSENSITIVE);
public static final Pattern RUNTIME_PATTERN = Pattern.compile("\\bruntime\\s*=", Pattern.CASE_INSENSITIVE);
Expand Down Expand Up @@ -1463,6 +1463,15 @@ public static String applyPlanner(String query, Planner planner, String cypherVe
return prependQueryOption(query, cypherPlanner, cypherVersion);
}

public static String applyRuntime(String query, String runtime, String cypherVersion) {
Matcher matcher = RUNTIME_PATTERN.matcher(query);
String cypherRuntime = String.format(" runtime=%s ", runtime.toLowerCase());
if (matcher.find()) {
return Util.prefixQueryWithCheck(cypherVersion, matcher.replaceFirst(cypherRuntime));
}
return prependQueryOption(query, cypherRuntime, cypherVersion);
}

private static String prependQueryOption(String query, String cypherOption, String cypherVersion) {
List<String> cypherPrefix = Util.extractCypherPrefix(query, cypherVersion);
if (Objects.equals(cypherPrefix.getFirst(), "")) {
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/apoc/export/csv/ExportCSV.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ public Stream<ExportProgressInfo> query(
: (Map<String, Object>) config.getOrDefault("params", Collections.emptyMap());

final String source;
try (final var result = tx.execute(Util.prefixQueryWithCheck(procedureCallContext, query), params)) {
query = Util.prefixQueryWithCheck(procedureCallContext, query);
try (final var result = tx.execute(query, params)) {
source = String.format("statement: cols(%d)", result.columns().size());
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/apoc/meta/Meta.java
Original file line number Diff line number Diff line change
Expand Up @@ -1137,9 +1137,9 @@ public Stream<GraphResult> graphOf(
Map<String, Object> config) {
MetaConfig metaConfig = new MetaConfig(config, false);
final SubGraph subGraph;
if (graph instanceof String) {
Result result =
tx.execute(Util.getCypherVersionPrefix(procedureCallContext) + " runtime=pipelined " + graph);
if (graph instanceof String query) {
Result result = tx.execute(
Util.applyRuntime(query, "pipelined", Util.getCypherVersionString(procedureCallContext)));
subGraph = CypherResultSubGraph.from(tx, result, metaConfig.isAddRelationshipsBetweenNodes());
} else if (graph instanceof Map) {
Map<String, Object> mGraph = (Map<String, Object>) graph;
Expand Down
39 changes: 39 additions & 0 deletions core/src/test/java/apoc/HelperProcedures.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package apoc;

import java.util.List;
import org.neo4j.internal.kernel.api.procs.ProcedureCallContext;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.UserFunction;

public class HelperProcedures {

@Context
public ProcedureCallContext procedureCallContext;

public static class CypherVersionCombinations {
public String outerVersion;
public String innerVersion;
public String result;

public CypherVersionCombinations(String outerVersion, String innerVersion, String result) {
this.outerVersion = outerVersion;
this.innerVersion = innerVersion;
this.result = result;
}
}

public static final List<CypherVersionCombinations> cypherVersions = List.of(
new CypherVersionCombinations("CYPHER 5", "", "CYPHER_5"),
new CypherVersionCombinations("CYPHER 5", "CYPHER 5", "CYPHER_5"),
new CypherVersionCombinations("CYPHER 5", "CYPHER 25", "CYPHER_25"),
new CypherVersionCombinations("CYPHER 25", "", "CYPHER_25"),
new CypherVersionCombinations("CYPHER 25", "CYPHER 25", "CYPHER_25"),
new CypherVersionCombinations("CYPHER 25", "CYPHER 5", "CYPHER_5"));

@UserFunction(name = "apoc.cypherVersion")
@Description("This test function returns a string of the Cypher Version that is was called with.")
public String cypherVersion() {
return procedureCallContext.calledwithQueryLanguage().name();
}
}
224 changes: 223 additions & 1 deletion core/src/test/java/apoc/cypher/CypherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import apoc.HelperProcedures;
import apoc.text.Strings;
import apoc.util.TestUtil;
import apoc.util.Util;
Expand Down Expand Up @@ -88,7 +89,13 @@ public class CypherTest {
public static void setUp() {
apocConfig().setProperty(APOC_IMPORT_FILE_ENABLED, true);
TestUtil.registerProcedure(
db, Cypher.class, Utils.class, CypherFunctions.class, Timeboxed.class, Strings.class);
db,
Cypher.class,
Utils.class,
CypherFunctions.class,
Timeboxed.class,
Strings.class,
HelperProcedures.class);
}

@After
Expand Down Expand Up @@ -621,4 +628,219 @@ private void assertNoOpenTransactions() {
"show transactions", Map.of(), r -> r.stream().toList());
assertThat(txs).satisfiesExactly(row -> assertEquals("show transactions", row.get("currentQuery")));
}

@Test
public void testDifferentCypherVersionsApocCase() {
// Test if case
for (String procName : List.of("case", "do.case")) {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.%s([true, '%s RETURN apoc.cypherVersion() AS version'])
""",
cypherVersion.outerVersion, procName, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

// Test else case
for (String procName : List.of("case", "do.case")) {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.%s([false, 'RETURN 1 AS version'], '%s RETURN apoc.cypherVersion() AS version')
""",
cypherVersion.outerVersion, procName, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}
}

@Test
public void testDifferentCypherVersionsApocWhen() {
// Test if case
for (String procName : List.of("when", "do.when")) {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.%s(true, '%s RETURN apoc.cypherVersion() AS version', 'RETURN 1')
""",
cypherVersion.outerVersion, procName, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

// Test else case
for (String procName : List.of("when", "do.when")) {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.%s(false, 'RETURN 1 AS version', '%s RETURN apoc.cypherVersion() AS version')
""",
cypherVersion.outerVersion, procName, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}
}

@Test
public void testDifferentCypherVersionsApocDoIt() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.cypher.doIt('%s RETURN apoc.cypherVersion() AS version', {})
""",
cypherVersion.outerVersion, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

@Test
public void testDifferentCypherVersionsApocRun() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.cypher.run('%s RETURN apoc.cypherVersion() AS version', {})
""",
cypherVersion.outerVersion, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

@Test
public void testDifferentCypherVersionsApocRunMany() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testResult(
db,
String.format(
"""
%s
CALL apoc.cypher.runMany('%s RETURN apoc.cypherVersion() AS version;\n %s RETURN apoc.cypherVersion() AS version;', {}, {statistics: false})
""",
cypherVersion.outerVersion, cypherVersion.innerVersion, cypherVersion.innerVersion),
r -> {
assertTrue(r.hasNext());
Map<String, Object> row = r.next();
assertEquals(cypherVersion.result, ((Map<String, Object>) row.get("result")).get("version"));
assertTrue(r.hasNext());
row = r.next();
assertEquals(cypherVersion.result, ((Map<String, Object>) row.get("result")).get("version"));
assertFalse(r.hasNext());
});
}
}

@Test
public void testDifferentCypherVersionsApocRunManyReadOnly() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testResult(
db,
String.format(
"""
%s
CALL apoc.cypher.runManyReadOnly('%s RETURN apoc.cypherVersion() AS version;\n %s RETURN apoc.cypherVersion() AS version;', {}, {statistics: false})
""",
cypherVersion.outerVersion, cypherVersion.innerVersion, cypherVersion.innerVersion),
r -> {
assertTrue(r.hasNext());
Map<String, Object> row = r.next();
assertEquals(cypherVersion.result, ((Map<String, Object>) row.get("result")).get("version"));
assertTrue(r.hasNext());
row = r.next();
assertEquals(cypherVersion.result, ((Map<String, Object>) row.get("result")).get("version"));
assertFalse(r.hasNext());
});
}
}

@Test
public void testDifferentCypherVersionsApocRunTimeboxed() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.cypher.runTimeboxed('%s RETURN apoc.cypherVersion() AS version;', {}, 100000)
""",
cypherVersion.outerVersion, cypherVersion.innerVersion, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

@Test
public void testDifferentCypherVersionsApocRunWrite() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
CALL apoc.cypher.runWrite('%s CREATE (n:Test {prop: apoc.cypherVersion()}) RETURN n.prop AS version;', {})
""",
cypherVersion.outerVersion, cypherVersion.innerVersion, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, ((Map<String, Object>) r.get("value")).get("version")));
}
}

@Test
public void testDifferentCypherVersionsApocRunFirstColumnSingle() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
RETURN apoc.cypher.runFirstColumnSingle('%s RETURN apoc.cypherVersion() AS version', {}) AS value
""",
cypherVersion.outerVersion, cypherVersion.innerVersion, cypherVersion.innerVersion),
r -> assertEquals(cypherVersion.result, r.get("value")));
}
}

@Test
public void testDifferentCypherVersionsApocRunFirstColumnMany() {
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCall(
db,
String.format(
"""
%s
RETURN apoc.cypher.runFirstColumnMany('%s UNWIND [1, 2] AS a RETURN apoc.cypherVersion() AS version', {}) AS value
""",
cypherVersion.outerVersion, cypherVersion.innerVersion),
r -> assertEquals(Arrays.asList(cypherVersion.result, cypherVersion.result), r.get("value")));
}
}

@Test
public void testDifferentCypherVersionsApocRunSchema() {
// This doesn't return anything, so just check it doesn't error :)
for (HelperProcedures.CypherVersionCombinations cypherVersion : HelperProcedures.cypherVersions) {
testCallEmpty(
db,
String.format(
"%s CALL apoc.cypher.runSchema('%s CREATE INDEX test IF NOT EXISTS FOR (w:Test) ON (w.name)',{})",
cypherVersion.outerVersion, cypherVersion.innerVersion),
Collections.emptyMap());
}
}
}
Loading

0 comments on commit 4449e2d

Please sign in to comment.