Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
**/target
**/ipfs-cache
**/data
**/sample_data
*.json
.env
**/.fastembed_cache
161 changes: 161 additions & 0 deletions grc20-core/src/mapping/entity/find_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use neo4rs::Path;

use crate::{
entity::EntityFilter,
error::DatabaseError,
mapping::{
order_by::FieldOrderBy,
query_utils::{
query_builder::{MatchQuery, QueryBuilder, Subquery},
VersionFilter,
},
AttributeFilter, PropFilter, Query,
},
system_ids::SCHEMA_TYPE,
};

pub struct Relation {
pub nodes_ids: Vec<String>,
pub relations_ids: Vec<String>,
}

pub struct FindPathQuery {
neo4j: neo4rs::Graph,
id1: String,
id2: String,
filter: EntityFilter,
order_by: Option<FieldOrderBy>,
limit: usize,
skip: Option<usize>,
space_id: Option<PropFilter<String>>,
version: VersionFilter,
}

impl FindPathQuery {
pub(super) fn new(neo4j: &neo4rs::Graph, id1: String, id2: String) -> Self {
Self {
neo4j: neo4j.clone(),
id1,
id2,
filter: EntityFilter::default(),
order_by: None,
limit: 100,
skip: None,
space_id: None,
version: VersionFilter::default(),
}
}

pub fn id(mut self, id: PropFilter<String>) -> Self {
self.filter.id = Some(id);
self
}

pub fn attribute(mut self, attribute: AttributeFilter) -> Self {
self.filter.attributes.push(attribute);
self
}

pub fn attribute_mut(&mut self, attribute: AttributeFilter) {
self.filter.attributes.push(attribute);
}

pub fn attributes(mut self, attributes: impl IntoIterator<Item = AttributeFilter>) -> Self {
self.filter.attributes.extend(attributes);
self
}

pub fn attributes_mut(&mut self, attributes: impl IntoIterator<Item = AttributeFilter>) {
self.filter.attributes.extend(attributes);
}

pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}

pub fn skip(mut self, skip: usize) -> Self {
self.skip = Some(skip);
self
}

/// Overwrite the current filter with a new one
pub fn with_filter(mut self, filter: EntityFilter) -> Self {
self.filter = filter;
self
}

pub fn order_by(mut self, order_by: FieldOrderBy) -> Self {
self.order_by = Some(order_by);
self
}

pub fn order_by_mut(&mut self, order_by: FieldOrderBy) {
self.order_by = Some(order_by);
}

pub fn space_id(mut self, space_id: impl Into<PropFilter<String>>) -> Self {
self.space_id = Some(space_id.into());
self
}

pub fn version(mut self, space_version: String) -> Self {
self.version.version_mut(space_version);
self
}

pub fn version_opt(mut self, space_version: Option<String>) -> Self {
self.version.version_opt(space_version);
self
}

fn subquery(&self) -> QueryBuilder {
QueryBuilder::default()
.subquery(MatchQuery::new(
"p = allShortestPaths((e1:Entity {id: $id1}) -[:RELATION*1..10]-(e2:Entity {id: $id2}))",
).r#where(format!("NONE(n IN nodes(p) WHERE EXISTS((n)-[:RELATION]-(:Entity {{id: \"{SCHEMA_TYPE}\"}})))")))//makes sure to not use primitive types
.limit(self.limit)
.params("id1", self.id1.clone())
.params("id2", self.id2.clone())
}
}

impl Query<Vec<Relation>> for FindPathQuery {
async fn send(self) -> Result<Vec<Relation>, DatabaseError> {
let query = self.subquery().r#return("p");

if cfg!(debug_assertions) || cfg!(test) {
println!(
"entity::FindPathQuery::<T>:\n{}\nparams:{:?}",
query.compile(),
[self.id1, self.id2]
);
}

let mut result = self.neo4j.execute(query.build()).await?;
let mut all_relationship_data = Vec::new();

// Process each row
while let Some(row) = result.next().await? {
let path: Path = row.get("p")?;
tracing::info!("This is the info for Path: {:?}", path);

let relationship_data: Relation = Relation {
nodes_ids: (path
.nodes()
.iter()
.filter_map(|rel| rel.get("id").ok())
.collect()),
relations_ids: (path
.rels()
.iter()
.filter_map(|rel| rel.get("relation_type").ok())
.collect()),
};

all_relationship_data.push(relationship_data);
}

Ok(all_relationship_data)
}
}
7 changes: 7 additions & 0 deletions grc20-core/src/mapping/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod delete_many;
pub mod delete_one;
pub mod find_many;
pub mod find_one;
pub mod find_path;
pub mod insert_many;
pub mod insert_one;
pub mod models;
Expand All @@ -11,6 +12,7 @@ pub mod utils;
pub use delete_one::DeleteOneQuery;
pub use find_many::FindManyQuery;
pub use find_one::FindOneQuery;
pub use find_path::FindPathQuery;
pub use insert_one::InsertOneQuery;
pub use models::{Entity, EntityNode, EntityNodeRef, SystemProperties};
pub use semantic_search::SemanticSearchQuery;
Expand Down Expand Up @@ -128,6 +130,11 @@ pub fn search<T>(neo4j: &neo4rs::Graph, vector: Vec<f64>) -> SemanticSearchQuery
SemanticSearchQuery::new(neo4j, vector)
}

// TODO: add docs for use via GraphQL
pub fn find_path(neo4j: &neo4rs::Graph, id1: String, id2: String) -> FindPathQuery {
FindPathQuery::new(neo4j, id1, id2)
}

pub fn insert_one<T>(
neo4j: &neo4rs::Graph,
block: &BlockMetadata,
Expand Down
5 changes: 3 additions & 2 deletions grc20-core/src/mapping/query_utils/attributes_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ impl AttributeFilter {
pub fn subquery(&self, node_var: &str) -> MatchQuery {
let attr_rel_var = format!("r_{node_var}_{}", self.attribute);
let attr_node_var = format!("{node_var}_{}", self.attribute);
let attr_id_var = format!("a_{node_var}_{}", self.attribute);

MatchQuery::new(
format!("({node_var}) -[{attr_rel_var}:ATTRIBUTE]-> ({attr_node_var}:Attribute {{id: $attribute}})")
format!("({node_var}) -[{attr_rel_var}:ATTRIBUTE]-> ({attr_node_var}:Attribute {{id: ${attr_id_var}}})")
)
.r#where(self.version.subquery(&attr_rel_var))
.where_opt(
Expand All @@ -118,6 +119,6 @@ impl AttributeFilter {
.where_opt(
self.value_type.as_ref().map(|value_type| value_type.subquery(&attr_node_var, "value_type", None))
)
.params("attribute", self.attribute.clone())
.params(attr_id_var, self.attribute.clone())
}
}
2 changes: 1 addition & 1 deletion grc20-core/src/mapping/relation/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ impl<'a> MatchOneRelation<'a> {
.subquery(format!("ORDER BY {edge_var}.index"))
.params("id", self.id)
.params("space_id", self.space_id)
.with(vec![from_node_var, edge_var, to_node_var], next)
.with(vec![from_node_var, edge_var.to_string(), to_node_var], next)
}
}
Loading