Created
March 31, 2023 15:03
-
-
Save michael-simons/d921f5a96f6f3416c1e2b54060322107 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//usr/bin/env jbang "$0" "$@" ; exit $? | |
//JAVA 17 | |
//DEPS org.neo4j.driver:neo4j-java-driver:5.7.0 | |
//DEPS com.fasterxml.jackson.core:jackson-databind:2.14.1 | |
package ac.simons; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.util.AbstractMap; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.function.BiConsumer; | |
import java.util.function.Function; | |
import java.util.stream.Collector; | |
import java.util.stream.Collectors; | |
import org.neo4j.driver.AuthTokens; | |
import org.neo4j.driver.Driver; | |
import org.neo4j.driver.GraphDatabase; | |
import org.neo4j.driver.Record; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.annotation.JsonInclude; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
public class ExecuteQueryApiDemo { | |
public static void main(String... a) throws IOException { | |
var start = Paths.get(a.length == 0 ? System.getProperty("user.dir") : a[0]); | |
try ( | |
var driver = GraphDatabase.driver("neo4j://localhost:7687", AuthTokens.basic("neo4j", "verysecret")); | |
) { | |
createData(start, driver); | |
System.out.println("--"); | |
// printWithApocAndComplexStatement(start, driver); | |
System.out.println("--"); | |
// intoHierachyAndPrint(driver); | |
List<String> names = driver.executableQuery("MATCH (n:Path) RETURN n.name AS name") | |
.execute(Collectors.mapping(r -> r.get("name").asString(), Collectors.toList())); | |
System.out.println(); | |
System.out.println(names); | |
} | |
} | |
private static void createData(Path root, Driver driver) throws IOException { | |
var paths = Files.walk(root) | |
.map(p -> Map.of( | |
"parent_id", p.getParent().toString(), | |
"id", p.toString(), | |
"name", p.getFileName().toString())) | |
.toList(); | |
driver | |
// creates an executable query | |
.executableQuery("MATCH (n) DETACH DELETE n") | |
// and executes it. There is no need to consume or close the result | |
.execute(); | |
// Using an eager result | |
var result = driver | |
// Again, creating the executable query | |
.executableQuery(""" | |
UNWIND $paths AS path WITH path | |
MERGE (c:Path {id: path.id, name: path.name}) | |
MERGE (p:Path {id: path.parent_id}) | |
MERGE (c)-[r:HAS_PARENT]->(p) | |
RETURN c, r, p | |
""") | |
// but enriching it with parameters | |
.withParameters(Map.of("paths", paths)) | |
.execute(); | |
// Gives you access to the result summary, including counters and more, | |
// no need to consume something upfront | |
var counters = result.summary().counters(); | |
System.out.println( | |
counters.nodesCreated() + " nodes and " + | |
counters.relationshipsCreated() + " relationships have been created"); | |
// The returned records are already materialized, iterating them multiple | |
// times is safe and does not involve multiple round trips | |
var c1 = result.records().stream() | |
.mapToInt(r -> r.get("c").get("name").asString().length()) | |
.summaryStatistics().getMax(); | |
var c2 = result.records().stream() | |
.mapToInt(r -> r.get("p").get("name").asString().length()) | |
.summaryStatistics().getMax(); | |
var format = "| %1$-" + c1 + "s | %2$-" + c2 + "s |%n"; | |
System.out.printf((format), "Name", "Parent"); | |
System.out.println("|" + "-".repeat(c1 + 2) + "|" + "-".repeat(c2 + 2) + "|"); | |
result.records().forEach(r -> { | |
var c = r.get("c").asNode(); | |
var p = r.get("p").asNode(); | |
System.out.printf(format, c.get("name").asString(), p.get("name").asString()); | |
}); | |
} | |
private static void printWithApocAndComplexStatement(Path root, Driver driver) { | |
var result = driver.executableQuery(""" | |
MATCH (r:Path {name: $nameOfRoot}) | |
MATCH (l:Path) WHERE NOT (EXISTS {MATCH (l)<-[:HAS_PARENT]-(:Path)}) | |
MATCH path=(r) <-[:HAS_PARENT*]-(l) | |
WITH collect(path) AS paths | |
CALL apoc.convert.toTree(paths, false, {nodes: {Path: ['-id']}}) YIELD value | |
RETURN apoc.convert.toJson(value) AS result | |
""") | |
.withParameters(Map.of("nameOfRoot", root.getFileName().toString())) | |
.execute(); | |
System.out.println(result.records().get(0).get("result").asString()); | |
} | |
record File( | |
@JsonIgnore String id, | |
String name, | |
@JsonInclude(JsonInclude.Include.NON_EMPTY) List<File> children) { | |
} | |
public static <K, E, R extends Record> Collector<R, ?, List<E>> intoHierarchy( | |
Function<? super R, ? extends K> keyMapper, | |
Function<? super R, ? extends K> parentKeyMapper, | |
Function<? super R, ? extends E> nodeMapper, | |
BiConsumer<? super E, ? super E> parentChildAppender | |
) { | |
return Collectors.collectingAndThen( | |
Collectors.toMap(keyMapper, r -> new AbstractMap.SimpleImmutableEntry<R, E>( | |
r, nodeMapper.apply(r) | |
)), | |
m -> { | |
List<E> r = new ArrayList<>(); | |
m.forEach((k, v) -> { | |
Map.Entry<R, E> parent = m.get( | |
parentKeyMapper.apply(v.getKey()) | |
); | |
if (parent != null) { | |
parentChildAppender.accept( | |
parent.getValue(), v.getValue() | |
); | |
} else { | |
r.add(v.getValue()); | |
} | |
}); | |
return r; | |
} | |
); | |
} | |
private static void intoHierachyAndPrint(Driver driver) throws IOException { | |
var result = driver | |
.executableQuery(""" | |
MATCH (p:Path) <-[:HAS_PARENT]-(c:Path) | |
RETURN | |
elementId(c) AS id, | |
elementId(p) AS parentId, | |
c.name AS name | |
""") | |
// This will take care of iterating a non-eager-result-set | |
// for us plus all the added benefits of using retries internally | |
// It won't allow us to take the non-eager-result set out of | |
// transaction scope which is an excellent thing | |
.execute(intoHierarchy( | |
r -> r.get("id").asString(), | |
r -> r.get("parentId").asString(), | |
r -> new File(r.get("id").asString(), r.get("name").asString(), new ArrayList<>()), | |
(p, c) -> p.children().add(c) | |
)); | |
new ObjectMapper() | |
.writerWithDefaultPrettyPrinter() | |
.writeValue(System.out, result); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment