Skip to content
Merged
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,13 @@ CFLAGS='-std=gnu17' cargo run --bin mcp-server -- \
--neo4j-pass neo4j
```

### Local testing with sample data
Start the neo4j database and run the following command:
```bash
CFLAGS='-std=gnu17' cargo run --example seed_data
```

The IDs of the sample data can be found in `sink/examples/seed_data.rs`.

## GRC20 CLI
Coming soon™️
2 changes: 1 addition & 1 deletion grc20-core/src/ids/system_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub const IMAGE: &str = "X8KB1uF84RYppghBSVvhqr";

/// Relation type. This is the entity representing the Join between the
/// the Collection and the Entity
pub const RELATION: &str = "AKDxovGvZaPSWnmKnSoZJY";
pub const RELATION_SCHEMA_TYPE: &str = "AKDxovGvZaPSWnmKnSoZJY";

pub const SPACE_TYPE: &str = "7gzF671tq5JTZ13naG4tnr";

Expand Down
84 changes: 83 additions & 1 deletion grc20-core/src/mapping/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use find_one::FindOneQuery;
pub use insert_one::InsertOneQuery;
pub use models::{Entity, EntityNode, EntityNodeRef, SystemProperties};
pub use semantic_search::SemanticSearchQuery;
pub use utils::{EntityFilter, EntityRelationFilter};
pub use utils::{EntityFilter, EntityRelationFilter, TypesFilter};

use crate::block::BlockMetadata;

Expand All @@ -34,14 +34,96 @@ pub fn delete_one(
)
}

/// Creates a query to find a single entity by its ID if it exists. Supports optional
/// filtering by space ID and version.
/// ```rust
/// use grc20_core::mapping::entity;
///
/// // Get current entity
/// let maybe_entity = entity::find_one::<EntityNode>(&neo4j, "entity_id")
/// .send()
/// .await?;
///
/// // Get entity in a specific space and version
/// let maybe_entity = entity::find_one::<EntityNode>(&neo4j, "entity_id")
/// .space_id("space_id")
/// .space_version("space_version")
/// .send()
/// .await?;
/// ```
pub fn find_one<T>(neo4j: &neo4rs::Graph, id: impl Into<String>) -> FindOneQuery<T> {
FindOneQuery::new(neo4j, id.into())
}

/// Creates a query to find multiple entities. Supports filtering by relations and
/// properties as well as ordering and pagination. See [`EntityFilter`] for more details
/// on filtering options.
///
/// ```rust
/// use grc20_core::mapping::entity;
/// use grc20_core::mapping::query_utils::order_by;
///
/// // Find entities with a specific attribute, order them by a property and
/// // return the first 10.
/// let entities = entity::find_many::<EntityNode>(&neo4j)
/// .filter(entity::EntityFilter::default()
/// // Filter by "SOME_ATTRIBUTE" attribute with value "some_value"
/// .attribute(AttributeFilter::new("SOME_ATTRIBUTE").value("some_value")))
/// .order_by(order_by::asc("some_property"))
/// .limit(10)
/// .send()
/// .await?;
///
/// // Find entities with a specific relation, in this case entities that have a
/// // `Parent` relation to an entity with ID "Alice".
/// let entities = entity::find_many::<EntityNode>(&neo4j)
/// .filter(entity::EntityFilter::default()
/// // Filter by relations
/// .relations(entity::EntityRelationFilter::default()
/// // Filter by `Parent` relation to entity with ID "Alice"
/// .relation_type("Parent".to_string())
/// .to_id("Alice".to_string())))
/// .send()
/// .await?;
///
/// // Find entities with a specific type (note: `TypesFilter` is a shorthand
/// // for `EntityRelationFilter`. It is converted to a relation filter internally).
/// let entities = entity::find_many::<EntityNode>(&neo4j)
/// .filter(entity::EntityFilter::default()
/// // Filter by `Types` relations pointing to `EntityType`
/// .relations(TypesFilter::default().r#type("EntityType".to_string())))
/// .send()
/// .await?;
/// ```
pub fn find_many<T>(neo4j: &neo4rs::Graph) -> FindManyQuery<T> {
FindManyQuery::new(neo4j)
}

/// Create a query to search for entities using semantic search based on a vector. The query
/// supports the same filtering options as `find_many`, allowing you to filter results by
/// attributes, relations, and other properties.
///
/// Important: The search uses *approximate* nearest neighbor search, which means that
/// the results with filtering applied after the search, which may lead to some results
/// that contain fewer than the desired quantity `limit`.
/// ```rust
/// use grc20_core::mapping::entity;
///
/// let search_vector = embedding::embed("my search query");
///
/// // Search for entities similar to the provided vector.
/// let results = entity::search::<EntityNode>(&neo4j, search_vector)
/// .send()
///
/// // Search for types (i.e.: entities that have `Types`` relation to `SchemaType``) of
/// // entities similar to the provided vector.
/// let results = entity::search::<EntityNode>(&neo4j, search_vector)
/// .filter(entity::EntityFilter::default()
/// // Filter by `Types` relations pointing to `SchemaType`
/// .relations(entity::TypesFilter::default().r#type(system_ids::SCHEMA_TYPE)))
/// .send()
/// .await?;
/// ```
pub fn search<T>(neo4j: &neo4rs::Graph, vector: Vec<f64>) -> SemanticSearchQuery<T> {
SemanticSearchQuery::new(neo4j, vector)
}
Expand Down
36 changes: 36 additions & 0 deletions grc20-core/src/mapping/entity/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
system_ids,
};

/// Filter used to find entities in the knowledge graph.
#[derive(Clone, Debug, Default)]
pub struct EntityFilter {
pub(crate) id: Option<PropFilter<String>>,
Expand Down Expand Up @@ -162,6 +163,41 @@ impl EntityRelationFilter {
}
}

#[derive(Clone, Debug, Default)]
pub struct TypesFilter {
types_contains: Vec<String>,
}

impl TypesFilter {
pub fn r#type(mut self, r#type: impl Into<String>) -> Self {
self.types_contains.push(r#type.into());
self
}

pub fn types(mut self, mut types: Vec<String>) -> Self {
self.types_contains.append(&mut types);
self
}
}

impl From<TypesFilter> for EntityRelationFilter {
fn from(types_filter: TypesFilter) -> Self {
let mut filter = EntityRelationFilter::default();

if !types_filter.types_contains.is_empty() {
filter = filter.relation_type(system_ids::TYPES_ATTRIBUTE);

if let [r#type] = &types_filter.types_contains[..] {
filter = filter.to_id(r#type.to_string());
} else {
filter = filter.to_id(types_filter.types_contains);
}
}

filter
}
}

#[derive(Clone, Debug)]
pub struct MatchEntityAttributes<'a> {
space_id: &'a Option<PropFilter<String>>,
Expand Down
2 changes: 0 additions & 2 deletions grc20-core/src/mapping/query_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ pub mod order_by;
pub mod prop_filter;
pub mod query_builder;
pub mod query_part;
pub mod types_filter;
pub mod version_filter;

pub use attributes_filter::AttributeFilter;
pub use order_by::{FieldOrderBy, OrderDirection};
pub use prop_filter::PropFilter;
pub use query_part::QueryPart;
pub use types_filter::TypesFilter;
pub use version_filter::VersionFilter;

pub trait Query<T>: Sized {
Expand Down
36 changes: 0 additions & 36 deletions grc20-core/src/mapping/query_utils/types_filter.rs

This file was deleted.

49 changes: 49 additions & 0 deletions grc20-core/src/mapping/relation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ pub fn delete_one(
)
}

/// Creates a query to find a single relation by its ID and space ID if it exists. Supports optional
/// filtering by version.
///
/// ```rust
/// use grc20_core::mapping::relation;
///
/// // Get current relation
/// let maybe_relation = relation::find_one::<Relation>(&neo4j, "relation_id", "space_id", None)
/// .send()
/// .await?;
///
/// // Get relation in a specific space and version
/// let maybe_relation = relation::find_one::<Relation>(&neo4j, "relation_id", "space_id", Some("space_version".to_string()))
/// .send()
/// .await?;
/// ```
pub fn find_one<T>(
neo4j: &neo4rs::Graph,
relation_id: impl Into<String>,
Expand All @@ -56,10 +72,40 @@ pub fn find_one<T>(
FindOneQuery::new(neo4j, relation_id.into(), space_id.into(), space_version)
}

/// Creates a query to find multiple relations. Supports filtering by relation_type and its to/from entities.
/// The results are ordered by relation index.
///
/// See [`RelationFilter`] for more details on filtering options.
///
/// ```rust
/// use grc20_core::mapping::relation;
/// use grc20_core::mapping::query_utils::order_by;
///
/// // Find relations of a specific type (e.g.: "Parent").
/// let relations = relation::find_many::<Relation>(&neo4j)
/// .filter(relation::RelationFilter::default()
/// // Filter by relation type "Parent" (we provide an entity filter with the ID "Parent")
/// .relation_type(entity::EntityFilter::default().id("Parent")))
/// .limit(10)
/// .send()
/// .await?;
///
/// // Find relations with a specific from entity, in this case relations that have a
/// // any type of relation between "Alice" and "Bob".
/// let relations = relation::find_many::<Relation>(&neo4j)
/// .filter(relation::RelationFilter::default()
/// // Filter by from entity with ID "Alice"
/// .from_(entity::EntityFilter::default().id("Alice"))
/// .to_(entity::EntityFilter::default().id("Bob")))
/// .send()
/// .await?;
/// ```
pub fn find_many<T>(neo4j: &neo4rs::Graph) -> FindManyQuery<T> {
FindManyQuery::new(neo4j)
}

/// Same as `find_one`, but it returns the `to` entity of the relation instead of the
/// relation itself.
pub fn find_one_to<T>(
neo4j: &neo4rs::Graph,
relation_id: impl Into<String>,
Expand All @@ -69,6 +115,9 @@ pub fn find_one_to<T>(
FindOneToQuery::new(neo4j, relation_id.into(), space_id.into(), space_version)
}

/// Same as `find_many`, but it returns the `to` entities of the relations instead of the
/// relations themselves. This is useful when you want to retrieve the target entities of
/// a set of relations without fetching the relations themselves.
pub fn find_many_to<T>(neo4j: &neo4rs::Graph) -> FindManyToQuery<T> {
FindManyToQuery::new(neo4j)
}
Expand Down
2 changes: 1 addition & 1 deletion grc20-macros/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ pub(crate) fn generate_query_impls(opts: &EntityOpts) -> TokenStream2 {
let schema_type = opts.schema_type.as_ref().map(|s| quote!(#s));
let type_filter = if let Some(schema_type) = schema_type {
quote! {
.relations(grc20_core::mapping::query_utils::TypesFilter::default().r#type(#schema_type.to_string()))
.relations(grc20_core::mapping::entity::TypesFilter::default().r#type(#schema_type.to_string()))
}
} else {
quote! {}
Expand Down
8 changes: 2 additions & 6 deletions grc20-sdk/src/models/space/space_types_query.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use futures::{Stream, TryStreamExt};

use grc20_core::{
entity,
entity::{self, TypesFilter},
error::DatabaseError,
mapping::{
prop_filter,
query_utils::{QueryStream, TypesFilter},
EntityFilter, EntityNode, PropFilter, Query,
},
mapping::{prop_filter, query_utils::QueryStream, EntityFilter, EntityNode, PropFilter, Query},
neo4rs, system_ids,
};

Expand Down
6 changes: 3 additions & 3 deletions mcp-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use clap::{Args, Parser};
use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};
use futures::TryStreamExt;
use grc20_core::{
entity::{self, Entity, EntityRelationFilter},
mapping::{Query, QueryStream, query_utils::TypesFilter},
entity::{self, Entity, EntityRelationFilter, TypesFilter},
mapping::{Query, QueryStream},
neo4rs, system_ids,
};
use grc20_sdk::models::BaseEntity;
Expand Down Expand Up @@ -190,7 +190,7 @@ impl KnowledgeGraph {
entity::EntityFilter::default().relations(
EntityRelationFilter::default()
.relation_type(system_ids::VALUE_TYPE_ATTRIBUTE)
.to_id(system_ids::RELATION),
.to_id(system_ids::RELATION_SCHEMA_TYPE),
),
)
.limit(8)
Expand Down
Loading