From 4ce37835ced49e40e31b7e8ef853aacbdf361f6e Mon Sep 17 00:00:00 2001 From: Andreas Eberhart Date: Mon, 13 Jan 2025 11:23:09 +0100 Subject: [PATCH] support * notation for all types of link traversals #323 --- .../java/org/dashjoin/util/OpenCypher.java | 58 +++++++++++++++++-- .../org/dashjoin/util/OpenCypherQuery.java | 2 +- .../dashjoin/util/OpenCypherQueryTest.java | 8 +-- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypher.java b/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypher.java index 517d92f15..26859ce2b 100644 --- a/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypher.java +++ b/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypher.java @@ -34,6 +34,8 @@ static class Relation { String name; String variable; Boolean left2right; + Integer from; + Integer to; } /** @@ -81,7 +83,6 @@ Table parseTable(OpenCypherQuery.Table t) { * query constructor - parsing is done in old code */ public OpenCypher(OpenCypherQuery query) { - System.out.println(query); this.query = query; patterns = new ArrayList<>(); @@ -103,6 +104,13 @@ public OpenCypher(OpenCypherQuery query) { p.right = parseTable(i.table); p.right.key = i.table.key; p.right.value = i.table.value; + if (i.edge.star) { + p.relation.from = i.edge.from == null ? 1 : i.edge.from; + p.relation.to = i.edge.to == null ? 5 : i.edge.to; + } else { + p.relation.from = 1; + p.relation.to = 1; + } left = p.right; } patterns.get(patterns.size() - 1).isLast = true; @@ -149,20 +157,56 @@ class Path { List bindings; boolean isSolution() { + Pattern last = bindings.get(bindings.size() - 1).pattern; + if (last.isLast) + if (last.relation != null) + if (numberOfMatches() < last.relation.from) + return false; return bindings.get(bindings.size() - 1).pattern.isLast; } + /** + * how often did the last pattern match + */ + int numberOfMatches() { + + Pattern last = bindings.get(bindings.size() - 1).pattern; + + int count = 0; + for (Binding b : bindings) + if (b.pattern == last) + count++; + + return count; + } + /** * given the current partial result, return the list of possible next patterns to traverse. This * can be a list, because there are constructs like "traverse this link 1 or 2 times" */ List candidatePatterns() { - // TODO: only take next pattern for now - Pattern next = bindings.get(bindings.size() - 1).pattern.next; - if (next == null) - return Arrays.asList(); - else + + Pattern last = bindings.get(bindings.size() - 1).pattern; + Pattern next = last.next; + + int count = 0; + for (Binding b : bindings) + if (b.pattern == last) + count++; + + if (last.relation == null) + // we are at the start, return next if there is one return Arrays.asList(next); + + if (last.relation.from < count) + // we still need more edges with this relation + return Arrays.asList(last); + + if (count < last.relation.to) + // we can either add another hop of the last relation or jump to the next + return Arrays.asList(last, next); + + return Arrays.asList(next); } /** @@ -187,6 +231,8 @@ void search(List> res) throws Exception { res.add(OpenCypher.this.query.project(r, path)); } for (Pattern pattern : candidatePatterns()) { + if (pattern == null) + continue; Binding b = bindings.get(bindings.size() - 1); if (pattern.relation.left2right == null || pattern.relation.left2right) { diff --git a/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypherQuery.java b/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypherQuery.java index 398632239..f57f593e1 100644 --- a/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypherQuery.java +++ b/dashjoin-core/src/main/java/org/dashjoin/util/OpenCypherQuery.java @@ -240,7 +240,7 @@ public static class Step { public Map edge; } - public boolean newEngine = false; + public boolean newEngine = true; /** * name of the path variable: MATCH path=(... diff --git a/dashjoin-core/src/test/java/org/dashjoin/util/OpenCypherQueryTest.java b/dashjoin-core/src/test/java/org/dashjoin/util/OpenCypherQueryTest.java index 03692bc68..fea957f07 100644 --- a/dashjoin-core/src/test/java/org/dashjoin/util/OpenCypherQueryTest.java +++ b/dashjoin-core/src/test/java/org/dashjoin/util/OpenCypherQueryTest.java @@ -7,7 +7,6 @@ import org.dashjoin.service.Services; import org.dashjoin.util.OpenCypherQuery.Table; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -283,7 +282,6 @@ public void testRange() throws Exception { Assertions.assertEquals("[{from.ID=4, to.ID=1}]", res.toString()); } - @Disabled @Test public void testRange2() throws Exception { List> res = @@ -291,10 +289,10 @@ public void testRange2() throws Exception { Assertions.assertEquals(4, res.size()); res = run("MATCH (from:`dj/junit/NODE` {ID:1})<-[*2..2]-(to) RETURN from.ID, to.ID"); - Assertions.assertEquals("[{from.ID=4, to.ID=1}]", res.toString()); + Assertions.assertEquals("[{from.ID=1, to.ID=4}]", res.toString()); res = run( - "MATCH (from:`dj/junit/NODE` {ID:4})<-[dj/junit/NODE/REL`*2..2]-(to) RETURN from.ID, to.ID"); - Assertions.assertEquals("[{from.ID=4, to.ID=1}]", res.toString()); + "MATCH (from:`dj/junit/NODE` {ID:1})<-[`dj/junit/NODE/REL`*2..2]-(to) RETURN from.ID, to.ID"); + Assertions.assertEquals("[{from.ID=1, to.ID=4}]", res.toString()); } }