|
@@ -26,11 +26,11 @@ import java.time.Duration;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
import java.util.Set;
|
|
|
import java.util.TreeMap;
|
|
import java.util.TreeMap;
|
|
|
-import java.util.TreeSet;
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Neo4j 动态 Driver 管理 + Cypher 执行
|
|
* Neo4j 动态 Driver 管理 + Cypher 执行
|
|
@@ -107,6 +107,7 @@ public class Neo4jExecutorService {
|
|
|
// 用 LinkedHashMap 去重(elementId 唯一)
|
|
// 用 LinkedHashMap 去重(elementId 唯一)
|
|
|
Map<String, Map<String, Object>> nodes = new LinkedHashMap<>();
|
|
Map<String, Map<String, Object>> nodes = new LinkedHashMap<>();
|
|
|
Map<String, Map<String, Object>> edges = new LinkedHashMap<>();
|
|
Map<String, Map<String, Object>> edges = new LinkedHashMap<>();
|
|
|
|
|
+ List<Map<String, Object>> records = new ArrayList<>();
|
|
|
|
|
|
|
|
TransactionConfig txConfig = TransactionConfig.builder()
|
|
TransactionConfig txConfig = TransactionConfig.builder()
|
|
|
.withTimeout(Duration.ofSeconds(props.getQueryTimeoutSeconds()))
|
|
.withTimeout(Duration.ofSeconds(props.getQueryTimeoutSeconds()))
|
|
@@ -115,9 +116,12 @@ public class Neo4jExecutorService {
|
|
|
try (var session = newSession(driver, gs.getDatabase())) {
|
|
try (var session = newSession(driver, gs.getDatabase())) {
|
|
|
var result = session.run(cypher, txConfig);
|
|
var result = session.run(cypher, txConfig);
|
|
|
for (Record record : result.list()) {
|
|
for (Record record : result.list()) {
|
|
|
|
|
+ Map<String, Object> row = new LinkedHashMap<>();
|
|
|
for (var pair : record.fields()) {
|
|
for (var pair : record.fields()) {
|
|
|
collectValue(pair.value(), nodes, edges, 0);
|
|
collectValue(pair.value(), nodes, edges, 0);
|
|
|
|
|
+ row.put(pair.key(), toSerializableValue(pair.value(), 0));
|
|
|
}
|
|
}
|
|
|
|
|
+ records.add(row);
|
|
|
// 提前截断检查
|
|
// 提前截断检查
|
|
|
if (nodes.size() > props.getMaxNodes() || edges.size() > props.getMaxEdges()) {
|
|
if (nodes.size() > props.getMaxNodes() || edges.size() > props.getMaxEdges()) {
|
|
|
break;
|
|
break;
|
|
@@ -139,6 +143,7 @@ public class Neo4jExecutorService {
|
|
|
Map<String, Object> response = new LinkedHashMap<>();
|
|
Map<String, Object> response = new LinkedHashMap<>();
|
|
|
response.put("nodes", nodeList);
|
|
response.put("nodes", nodeList);
|
|
|
response.put("edges", edgeList);
|
|
response.put("edges", edgeList);
|
|
|
|
|
+ response.put("records", records);
|
|
|
response.put("truncated", truncated);
|
|
response.put("truncated", truncated);
|
|
|
response.put("durationMs", durationMs);
|
|
response.put("durationMs", durationMs);
|
|
|
log.info("GraphSource id={} 查询完成:{} nodes / {} edges in {} ms",
|
|
log.info("GraphSource id={} 查询完成:{} nodes / {} edges in {} ms",
|
|
@@ -167,8 +172,7 @@ public class Neo4jExecutorService {
|
|
|
Driver driver = getOrCreateDriver(gs);
|
|
Driver driver = getOrCreateDriver(gs);
|
|
|
Map<String, Map<String, List<String>>> nodeProperties = new TreeMap<>();
|
|
Map<String, Map<String, List<String>>> nodeProperties = new TreeMap<>();
|
|
|
Map<String, Map<String, List<String>>> relationshipProperties = new TreeMap<>();
|
|
Map<String, Map<String, List<String>>> relationshipProperties = new TreeMap<>();
|
|
|
- Map<String, Set<String>> starts = new TreeMap<>();
|
|
|
|
|
- Map<String, Set<String>> ends = new TreeMap<>();
|
|
|
|
|
|
|
+ Map<String, Set<GraphSchemaSnapshot.RelationshipEndpoint>> endpoints = new TreeMap<>();
|
|
|
try (var session = newSession(driver, gs.getDatabase())) {
|
|
try (var session = newSession(driver, gs.getDatabase())) {
|
|
|
var nodes = session.run("CALL db.schema.nodeTypeProperties() " +
|
|
var nodes = session.run("CALL db.schema.nodeTypeProperties() " +
|
|
|
"YIELD nodeLabels, propertyName, propertyTypes " +
|
|
"YIELD nodeLabels, propertyName, propertyTypes " +
|
|
@@ -203,8 +207,9 @@ public class Neo4jExecutorService {
|
|
|
while (directions.hasNext()) {
|
|
while (directions.hasNext()) {
|
|
|
Record record = directions.next();
|
|
Record record = directions.next();
|
|
|
String type = record.get("relationshipType").asString();
|
|
String type = record.get("relationshipType").asString();
|
|
|
- starts.computeIfAbsent(type, ignored -> new TreeSet<>()).add(record.get("startLabel").asString());
|
|
|
|
|
- ends.computeIfAbsent(type, ignored -> new TreeSet<>()).add(record.get("endLabel").asString());
|
|
|
|
|
|
|
+ endpoints.computeIfAbsent(type, ignored -> new LinkedHashSet<>())
|
|
|
|
|
+ .add(new GraphSchemaSnapshot.RelationshipEndpoint(
|
|
|
|
|
+ record.get("startLabel").asString(), record.get("endLabel").asString()));
|
|
|
relationshipProperties.computeIfAbsent(type, ignored -> new TreeMap<>());
|
|
relationshipProperties.computeIfAbsent(type, ignored -> new TreeMap<>());
|
|
|
}
|
|
}
|
|
|
} catch (Neo4jException e) {
|
|
} catch (Neo4jException e) {
|
|
@@ -215,9 +220,14 @@ public class Neo4jExecutorService {
|
|
|
.map(entry -> new GraphSchemaSnapshot.NodeSchema(entry.getKey(), entry.getValue()))
|
|
.map(entry -> new GraphSchemaSnapshot.NodeSchema(entry.getKey(), entry.getValue()))
|
|
|
.toList();
|
|
.toList();
|
|
|
List<GraphSchemaSnapshot.RelationshipSchema> relationships = relationshipProperties.entrySet().stream()
|
|
List<GraphSchemaSnapshot.RelationshipSchema> relationships = relationshipProperties.entrySet().stream()
|
|
|
- .map(entry -> new GraphSchemaSnapshot.RelationshipSchema(entry.getKey(),
|
|
|
|
|
- new ArrayList<>(starts.getOrDefault(entry.getKey(), Set.of())),
|
|
|
|
|
- new ArrayList<>(ends.getOrDefault(entry.getKey(), Set.of())), entry.getValue()))
|
|
|
|
|
|
|
+ .map(entry -> {
|
|
|
|
|
+ List<GraphSchemaSnapshot.RelationshipEndpoint> pairs =
|
|
|
|
|
+ new ArrayList<>(endpoints.getOrDefault(entry.getKey(), Set.of()));
|
|
|
|
|
+ return new GraphSchemaSnapshot.RelationshipSchema(entry.getKey(),
|
|
|
|
|
+ pairs.stream().map(GraphSchemaSnapshot.RelationshipEndpoint::startLabel).distinct().toList(),
|
|
|
|
|
+ pairs.stream().map(GraphSchemaSnapshot.RelationshipEndpoint::endLabel).distinct().toList(),
|
|
|
|
|
+ entry.getValue(), pairs);
|
|
|
|
|
+ })
|
|
|
.toList();
|
|
.toList();
|
|
|
return new GraphSchemaSnapshot(nodes, relationships);
|
|
return new GraphSchemaSnapshot(nodes, relationships);
|
|
|
}
|
|
}
|
|
@@ -321,10 +331,40 @@ public class Neo4jExecutorService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private Object toSerializableValue(Value value, int depth) {
|
|
|
|
|
+ if (depth > MAX_RECURSION_DEPTH || value == null || value.isNull()) return null;
|
|
|
|
|
+ String typeName;
|
|
|
|
|
+ try {
|
|
|
|
|
+ typeName = value.type().name();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return switch (typeName) {
|
|
|
|
|
+ case "NODE" -> nodeData(value.asNode());
|
|
|
|
|
+ case "RELATIONSHIP" -> relationshipData(value.asRelationship());
|
|
|
|
|
+ case "PATH" -> {
|
|
|
|
|
+ Path path = value.asPath();
|
|
|
|
|
+ List<Map<String, Object>> pathNodes = new ArrayList<>();
|
|
|
|
|
+ List<Map<String, Object>> pathEdges = new ArrayList<>();
|
|
|
|
|
+ path.nodes().forEach(node -> pathNodes.add(nodeData(node)));
|
|
|
|
|
+ path.relationships().forEach(rel -> pathEdges.add(relationshipData(rel)));
|
|
|
|
|
+ yield Map.of("nodes", pathNodes, "edges", pathEdges);
|
|
|
|
|
+ }
|
|
|
|
|
+ case "LIST" -> value.asList(item -> toSerializableValue(item, depth + 1));
|
|
|
|
|
+ case "MAP" -> value.asMap(item -> toSerializableValue(item, depth + 1));
|
|
|
|
|
+ default -> value.asObject();
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void putNode(Node node, Map<String, Map<String, Object>> nodes) {
|
|
private void putNode(Node node, Map<String, Map<String, Object>> nodes) {
|
|
|
String id = node.elementId();
|
|
String id = node.elementId();
|
|
|
if (nodes.containsKey(id)) return;
|
|
if (nodes.containsKey(id)) return;
|
|
|
|
|
+ nodes.put(id, nodeData(node));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> nodeData(Node node) {
|
|
|
Map<String, Object> data = new LinkedHashMap<>();
|
|
Map<String, Object> data = new LinkedHashMap<>();
|
|
|
|
|
+ String id = node.elementId();
|
|
|
data.put("id", id);
|
|
data.put("id", id);
|
|
|
// label:取第一个 label 或回退到 id
|
|
// label:取第一个 label 或回退到 id
|
|
|
List<String> labels = new ArrayList<>();
|
|
List<String> labels = new ArrayList<>();
|
|
@@ -336,20 +376,25 @@ public class Neo4jExecutorService {
|
|
|
Object display = pickDisplay(props);
|
|
Object display = pickDisplay(props);
|
|
|
if (display != null) data.put("label", String.valueOf(display));
|
|
if (display != null) data.put("label", String.valueOf(display));
|
|
|
data.put("properties", props);
|
|
data.put("properties", props);
|
|
|
- nodes.put(id, data);
|
|
|
|
|
|
|
+ return data;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void putRelationship(Relationship rel, Map<String, Map<String, Object>> edges) {
|
|
private void putRelationship(Relationship rel, Map<String, Map<String, Object>> edges) {
|
|
|
String id = rel.elementId();
|
|
String id = rel.elementId();
|
|
|
if (edges.containsKey(id)) return;
|
|
if (edges.containsKey(id)) return;
|
|
|
|
|
+ edges.put(id, relationshipData(rel));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> relationshipData(Relationship rel) {
|
|
|
Map<String, Object> data = new LinkedHashMap<>();
|
|
Map<String, Object> data = new LinkedHashMap<>();
|
|
|
|
|
+ String id = rel.elementId();
|
|
|
data.put("id", id);
|
|
data.put("id", id);
|
|
|
data.put("source", rel.startNodeElementId());
|
|
data.put("source", rel.startNodeElementId());
|
|
|
data.put("target", rel.endNodeElementId());
|
|
data.put("target", rel.endNodeElementId());
|
|
|
data.put("label", rel.type());
|
|
data.put("label", rel.type());
|
|
|
data.put("type", rel.type());
|
|
data.put("type", rel.type());
|
|
|
data.put("properties", rel.asMap());
|
|
data.put("properties", rel.asMap());
|
|
|
- edges.put(id, data);
|
|
|
|
|
|
|
+ return data;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 优先选 name / title / label 作为显示 */
|
|
/** 优先选 name / title / label 作为显示 */
|