Skip to content

Commit

Permalink
Allow to fetch shared nodes between a start node and other nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
yjcyxky committed Mar 6, 2024
1 parent d13ab06 commit 81f6f9c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 55 deletions.
3 changes: 3 additions & 0 deletions src/api/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,7 @@ impl BiomedgpsApi {
pool: Data<&Arc<neo4rs::Graph>>,
node_ids: Query<String>,
target_node_types: Query<Option<String>>,
start_node_id: Query<Option<String>>,
topk: Query<Option<u64>>,
nhops: Query<Option<usize>>,
nums_shared_by: Query<Option<u64>>,
Expand All @@ -1282,6 +1283,7 @@ impl BiomedgpsApi {
let pool_arc = pool.clone();
let node_ids = node_ids.0;
let target_node_types = target_node_types.0;
let start_node_id = start_node_id.0;

match NodeIdsQuery::new(&node_ids) {
Ok(_) => {}
Expand Down Expand Up @@ -1335,6 +1337,7 @@ impl BiomedgpsApi {
nhops as usize,
topk as usize,
nums_shared_by as usize,
start_node_id.as_deref(),
)
.await
{
Expand Down
148 changes: 98 additions & 50 deletions src/query_builder/cypher_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,19 @@ fn gen_nhops_query_str(
end_node_id: &str,
nhops: usize,
) -> String {
let hop_str = match nhops {
1 => "*1",
2 => "*1..2",
_ => "",
};

let query_str = format!(
"MATCH path = (n:{})-[r*..{}]-(m:{}) WHERE n.id IN ['{}'] AND m.id IN ['{}'] UNWIND nodes(path) AS node UNWIND relationships(path) AS edge RETURN DISTINCT node, edge",
start_node_type,
nhops,
end_node_type,
start_node_id,
end_node_id,
"MATCH path = (n:{start_node_type})-[r{hop_str}]-(m:{end_node_type}) WHERE n.id IN ['{start_node_id}'] AND m.id IN ['{end_node_id}'] UNWIND nodes(path) AS node UNWIND relationships(path) AS edge RETURN DISTINCT node, edge",
start_node_type = start_node_type,
hop_str = hop_str,
end_node_type = end_node_type,
start_node_id = start_node_id,
end_node_id = end_node_id
);

query_str
Expand Down Expand Up @@ -243,31 +249,8 @@ pub async fn query_shared_nodes(
nhops: usize,
topk: usize,
nums_shared_by: usize,
start_node_id: Option<&str>,
) -> Result<(Vec<NodeData>, Vec<EdgeData>), anyhow::Error> {
// Example query string:
// WITH ['MONDO:0100233', 'MONDO:0005404'] AS diseaseIds
// UNWIND diseaseIds AS diseaseId
// MATCH (start:Disease) WHERE start.id = diseaseId
// WITH COLLECT(DISTINCT start) AS startNodes
// UNWIND startNodes AS startNode
// MATCH p=(startNode)-[r*1]-(common:Disease)
// WHERE NOT startNode = common AND ALL(x IN nodes(p) WHERE x IN startNodes OR x = common)
// WITH common, COLLECT(DISTINCT startNode) AS relatedStartNodes, COLLECT(DISTINCT r) AS relations, COUNT(DISTINCT startNode) AS sharedBy
// WHERE sharedBy = 2
// RETURN common, relatedStartNodes, relations
// ORDER BY sharedBy DESC
// LIMIT 100

// Build the startNodesDetails string
let mut start_nodes_details = String::new();
for (i, node_id) in node_ids.iter().enumerate() {
let (node_type, node_id) = split_id(node_id)?;
start_nodes_details.push_str(&format!("{{label: '{}', id: '{}'}}", node_type, node_id));
if i < node_ids.len() - 1 {
start_nodes_details.push_str(", ");
}
}

let nums_shared_by = if nums_shared_by == 0 || nums_shared_by > node_ids.len() {
node_ids.len()
} else {
Expand All @@ -291,26 +274,91 @@ pub async fn query_shared_nodes(
_ => "",
};

let query_str = format!("
WITH [{start_nodes_details}] AS startNodesDetails
UNWIND startNodesDetails AS nodeDetails
MATCH (start)
WHERE start.id = nodeDetails.id AND ANY(label IN labels(start) WHERE label = nodeDetails.label)
WITH COLLECT(DISTINCT start) AS startNodes
UNWIND startNodes AS startNode
MATCH p=(startNode)-[r{hop_str}]-(common)
WHERE NOT startNode = common AND ALL(x IN nodes(p) WHERE x IN startNodes OR x = common) AND startNode IN startNodes
WITH common, COLLECT(DISTINCT startNode) AS relatedStartNodes, COLLECT(DISTINCT r) AS relations, COUNT(DISTINCT startNode) AS sharedBy
WHERE {where_clauses}
WITH common, relatedStartNodes, relations, sharedBy
ORDER BY sharedBy DESC
LIMIT {topk}
RETURN common, relatedStartNodes, relations",
topk = topk,
start_nodes_details = start_nodes_details,
hop_str = hop_str,
where_clauses = where_clauses
);
let query_str = if start_node_id.is_none() {
// Example query string:
// WITH ['MONDO:0100233', 'MONDO:0005404'] AS diseaseIds
// UNWIND diseaseIds AS diseaseId
// MATCH (start:Disease) WHERE start.id = diseaseId
// WITH COLLECT(DISTINCT start) AS startNodes
// UNWIND startNodes AS startNode
// MATCH p=(startNode)-[r*1]-(common:Disease)
// WHERE NOT startNode = common AND ALL(x IN nodes(p) WHERE x IN startNodes OR x = common)
// WITH common, COLLECT(DISTINCT startNode) AS relatedStartNodes, COLLECT(DISTINCT r) AS relations, COUNT(DISTINCT startNode) AS sharedBy
// WHERE sharedBy = 2
// RETURN common, relatedStartNodes, relations
// ORDER BY sharedBy DESC
// LIMIT 100

// Build the startNodesDetails string
let mut start_nodes_details = String::new();
for (i, node_id) in node_ids.iter().enumerate() {
let (node_type, node_id) = split_id(node_id)?;
start_nodes_details.push_str(&format!("{{label: '{}', id: '{}'}}", node_type, node_id));
if i < node_ids.len() - 1 {
start_nodes_details.push_str(", ");
}
};

format!("
WITH [{start_nodes_details}] AS startNodesDetails
UNWIND startNodesDetails AS nodeDetails
MATCH (start)
WHERE start.id = nodeDetails.id AND ANY(label IN labels(start) WHERE label = nodeDetails.label)
WITH COLLECT(DISTINCT start) AS startNodes
UNWIND startNodes AS startNode
MATCH p=(startNode)-[r{hop_str}]-(common)
WHERE NOT startNode = common AND ALL(x IN nodes(p) WHERE x IN startNodes OR x = common) AND startNode IN startNodes
WITH common, COLLECT(DISTINCT startNode) AS relatedStartNodes, COLLECT(DISTINCT r) AS relations, COUNT(DISTINCT startNode) AS sharedBy
WHERE {where_clauses}
WITH common, relatedStartNodes, relations, sharedBy
ORDER BY sharedBy DESC
LIMIT {topk}
RETURN common, relatedStartNodes, relations",
topk = topk,
start_nodes_details = start_nodes_details,
hop_str = hop_str,
where_clauses = where_clauses
)
} else {
let start_node_id = start_node_id.unwrap();
let (start_node_label, start_node_id) = split_id(start_node_id)?;
let other_nodes_details = node_ids
.iter()
.filter(|id| *id != &start_node_id)
.map(|id| {
let (label, id) = split_id(id).unwrap();
format!("{{label: '{}', id: '{}'}}", label, id)
})
.collect::<Vec<String>>()
.join(", ");

format!("
// 假设 start_node_id 和 start_node_label 代表我们感兴趣的起始节点的ID和标签
MATCH (start)
WHERE start.id = '{start_node_id}' AND ANY(label IN labels(start) WHERE label = '{start_node_label}')
WITH start AS startNode
// 这里不再需要收集起始节点,因为我们只有一个起始节点
UNWIND [{other_nodes_details}] AS otherNodeDetails
MATCH (other)
WHERE other.id = otherNodeDetails.id AND ANY(label IN labels(other) WHERE label = otherNodeDetails.label)
WITH COLLECT(DISTINCT other) AS otherNodes, startNode
UNWIND otherNodes AS otherNode
MATCH p=(startNode)-[r{hop_str}]-(common)
WHERE NOT startNode = common AND ALL(x IN nodes(p) WHERE x IN otherNodes OR x = common) AND startNode = startNode
WITH common, COLLECT(DISTINCT otherNode) AS relatedOtherNodes, COLLECT(DISTINCT r) AS relations, COUNT(DISTINCT otherNode) AS sharedBy
WHERE {where_clauses}
WITH common, relatedOtherNodes, relations, sharedBy
ORDER BY sharedBy DESC
LIMIT {topk}
RETURN common, relatedOtherNodes, relations",
topk = topk,
other_nodes_details = other_nodes_details, // 该变量需要提供其他节点的详情,例如ID和标签
hop_str = hop_str,
where_clauses = where_clauses,
start_node_id = start_node_id, // 指定的起始节点ID
start_node_label = start_node_label // 指定的起始节点标签
)
};

info!("query_shared_nodes's query_str: {}", query_str);
let mut result = graph.execute(query(&query_str)).await?;
Expand Down
2 changes: 1 addition & 1 deletion studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"antd": "5.7.0",
"antd-schema-form": "^4.5.1",
"axios": "^1.1.2",
"biominer-components": "0.2.76",
"biominer-components": "0.2.77",
"classnames": "^2.3.0",
"handlebars": "^4.7.7",
"handsontable": "^12.1.3",
Expand Down
8 changes: 4 additions & 4 deletions studio/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5772,10 +5772,10 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"

[email protected].76:
version "0.2.76"
resolved "https://registry.yarnpkg.com/biominer-components/-/biominer-components-0.2.76.tgz#6c58e0099acdd53cb9a1627186ef1afdb6877cff"
integrity sha512-0KkvOuDZoQAFhb9nKCPV5X3XCuOe7AswtqVV/T0PweUzhm0gP6puaiAew/p0pipXSqqexEReEdxxkpZHnb1Rfg==
[email protected].77:
version "0.2.77"
resolved "https://registry.yarnpkg.com/biominer-components/-/biominer-components-0.2.77.tgz#ec89d0463140993509a7eb20f876d1bc8428bba4"
integrity sha512-RqE19JGB9J12dXm6l6Q16nVxU1pLeFI+9FC70hXRFYSYSaYkJnGlRWy/qjn7KWRsWiJXGA4hZjmfAdVzM1mI5g==
dependencies:
"@fingerprintjs/fingerprintjs" "^3.4.2"
"@types/openseadragon" "^3.0.3"
Expand Down

0 comments on commit 81f6f9c

Please sign in to comment.