diff --git a/python/python/raphtory/filter/__init__.pyi b/python/python/raphtory/filter/__init__.pyi index 345f300486..9d06c8401c 100644 --- a/python/python/raphtory/filter/__init__.pyi +++ b/python/python/raphtory/filter/__init__.pyi @@ -24,17 +24,14 @@ import pyvis # type: ignore __all__ = [ "FilterExpr", + "FilterOps", "PropertyFilterOps", "Node", - "EdgeFilterOp", - "EdgeEndpoint", "Edge", + "EdgeEndpoint", + "EdgeFilterOp", + "EdgeIdFilterOp", "ExplodedEdge", - "FilterOps", - "PropertyFilterOps", - "NodeWindow", - "EdgeWindow", - "ExplodedEdgeWindow", ] class FilterExpr(object): @@ -53,45 +50,7 @@ class FilterExpr(object): def __ror__(self, value): """Return value|self.""" -class PropertyFilterOps(FilterOps): - def temporal(self): ... - -class Node(object): - @staticmethod - def id(): - """ - Filter node by id - - Returns: - NodeFilterBuilder: A filter builder for filtering by node id - """ - - @staticmethod - def metadata(name): ... - @staticmethod - def name(): - """ - Filter node by name - - Returns: - NodeFilterBuilder: A filter builder for filtering by node name - """ - - @staticmethod - def node_type(): - """ - Filter node by type - - Returns: - NodeFilterBuilder: A filter builder for filtering by node type - """ - - @staticmethod - def property(name): ... - @staticmethod - def window(py_start, py_end): ... - -class EdgeFilterOp(object): +class FilterOps(object): def __eq__(self, value): """Return self==value.""" @@ -110,39 +69,62 @@ class EdgeFilterOp(object): def __ne__(self, value): """Return self!=value.""" + def all(self): ... + def any(self): ... + def avg(self): ... def contains(self, value): ... def ends_with(self, value): ... - def fuzzy_search(self, value, levenshtein_distance, prefix_match): ... + def first(self): ... + def fuzzy_search(self, prop_value, levenshtein_distance, prefix_match): ... def is_in(self, values): ... + def is_none(self): ... def is_not_in(self, values): ... + def is_some(self): ... + def last(self): ... + def len(self): ... + def max(self): ... + def min(self): ... def not_contains(self, value): ... def starts_with(self, value): ... + def sum(self): ... -class EdgeEndpoint(object): - def id(self): ... - def name(self): ... +class PropertyFilterOps(FilterOps): + def temporal(self): ... -class Edge(object): +class Node(object): @staticmethod - def dst(): ... + def id(): ... @staticmethod def metadata(name): ... @staticmethod - def property(name): ... + def name(): ... @staticmethod - def src(): ... + def node_type(): ... + @staticmethod + def property(name): ... @staticmethod - def window(py_start, py_end): ... + def window(start, end): ... -class ExplodedEdge(object): +class Edge(object): + @staticmethod + def dst(): ... @staticmethod def metadata(name): ... @staticmethod def property(name): ... @staticmethod - def window(py_start, py_end): ... + def src(): ... + @staticmethod + def window(start, end): ... -class FilterOps(object): +class EdgeEndpoint(object): + def id(self): ... + def metadata(self, name): ... + def name(self): ... + def node_type(self): ... + def property(self, name): ... + +class EdgeFilterOp(object): def __eq__(self, value): """Return self==value.""" @@ -161,36 +143,45 @@ class FilterOps(object): def __ne__(self, value): """Return self!=value.""" - def all(self): ... - def any(self): ... - def avg(self): ... def contains(self, value): ... def ends_with(self, value): ... - def first(self): ... - def fuzzy_search(self, prop_value, levenshtein_distance, prefix_match): ... + def fuzzy_search(self, value, levenshtein_distance, prefix_match): ... def is_in(self, values): ... - def is_none(self): ... def is_not_in(self, values): ... - def is_some(self): ... - def last(self): ... - def len(self): ... - def max(self): ... - def min(self): ... def not_contains(self, value): ... def starts_with(self, value): ... - def sum(self): ... -class PropertyFilterOps(FilterOps): - def temporal(self): ... +class EdgeIdFilterOp(object): + def __eq__(self, value): + """Return self==value.""" -class NodeWindow(object): - def metadata(self, name): ... - def property(self, name): ... + def __ge__(self, value): + """Return self>=value.""" -class EdgeWindow(object): - def metadata(self, name): ... - def property(self, name): ... + def __gt__(self, value): + """Return self>value.""" -class ExplodedEdgeWindow(object): - def metadata(self, name): ... - def property(self, name): ... + def __le__(self, value): + """Return self<=value.""" + + def __lt__(self, value): + """Return self 55 + result = sorted(graph.filter(expr).edges.id) + expected = sorted( + [("2", "3")] + ) # edge (2,3) is at times 2 and 3; IDs dedup by view + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_property_temporal_sum(): + def check(graph): + expr = filter.Edge.src().property("prop6").temporal().last().sum() == 12 + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d")]) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_property_any_equals(): + def check(graph): + # src node "d" doesn't exist as src; src of edges are a,b,c; node "a" has prop8 = [2,3,3] + expr = filter.Edge.src().property("prop8").temporal().any().any() == 3 + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d")]) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_metadata_sum_with_same_name_property(): + def check(graph): + # src node "a" metadata prop1 sum == 36 + # src node "b" and "c" property prop1 < 36, they don't appear because it's property not metadata + expr = filter.Edge.src().metadata("prop1").sum() <= 36 + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d")]) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_metadata_avg(): + def check(graph): + expr = filter.Edge.src().metadata("prop2").avg() <= 2.0 + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d"), ("b", "d")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_property_and_edge_property(): + def check(graph): + expr = (filter.Edge.src().property("p2") == 2) & ( + filter.Edge.property("p20").temporal().any().contains("ship") + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "3")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_and_dst_property(): + def check(graph): + expr = (filter.Edge.src().property("p2") == 2) & ( + filter.Edge.dst().property("p20").contains("boat") + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "3")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_or_dst_property(): + def check(graph): + expr = (filter.Edge.src().property("p2") == 2) | ( + filter.Edge.dst().property("p20").contains("boat") + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "1"), ("2", "3")]) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_property_and_dst_name(): + def check(graph): + expr = (filter.Edge.src().property("prop1") >= 20) & ( + filter.Edge.dst().name() == "d" + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d"), ("c", "d")]) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_metadata_and_edge_property(): + def check(graph): + expr = (filter.Edge.src().metadata("prop1").sum() == 36) & ( + filter.Edge.property("eprop1") > 20 + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d")]) + assert result == expected + + return check + + +# node_type endpoint filters +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_node_type_eq(): + def check(graph): + expr = filter.Edge.src().node_type() == "fire_nation" + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("1", "2"), ("3", "1"), ("3", "4")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_dst_node_type_contains(): + def check(graph): + expr = filter.Edge.dst().node_type().contains("nomads") + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("1", "2")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_and_dst_node_type(): + def check(graph): + expr = filter.Edge.src().node_type().starts_with( + "fire" + ) & filter.Edge.dst().node_type().contains("nomads") + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("1", "2")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_or_dst_node_type(): + def check(graph): + expr = (filter.Edge.src().node_type() == "air_nomads") | ( + filter.Edge.dst().node_type() == "fire_nation" + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "3"), ("2", "1"), ("3", "1")]) + assert result == expected + + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_not_src_node_type(): + def check(graph): + expr = ~filter.Edge.src().node_type().contains("fire") + result = sorted(graph.filter(expr).edges.id) + expected = sorted( + [ + ("2", "3"), + ("2", "1"), + ("David Gilmour", "John Mayer"), + ("John Mayer", "Jimmy Page"), + ] + ) + assert result == expected + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_edges_src_node_type_eq_compose_with_edge_prop(): + def check(graph): + # edge ("c","d") has eprop1 > 20 but src node_type isn't fire_nation, so not included here + expr = (filter.Edge.src().node_type() == "fire_nation") & ( + filter.Edge.property("eprop1") > 20 + ) + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("a", "d")]) + assert result == expected + + return check diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_edge_filter_gql.py b/python/tests/test_base_install/test_graphql/test_filters/test_edge_filter_gql.py index b15f64ac2a..66f168bbb5 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_edge_filter_gql.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_edge_filter_gql.py @@ -14,8 +14,10 @@ def test_filter_edges_with_str_ids_for_node_id_eq_gql(graph): graph(path: "g") { filterEdges(expr: { src: { - field: NODE_ID - where: { eq: { str: "3" } } + node: { + field: NODE_ID + where: { eq: { str: "3" } } + } } }) { edges { @@ -54,8 +56,10 @@ def test_filter_edges_with_num_ids_for_node_id_eq_gql(graph): graph(path: "g") { filterEdges(expr: { src: { - field: NODE_ID - where: { eq: { u64: 1 } } + node: { + field: NODE_ID + where: { eq: { u64: 1 } } + } } }) { edges { @@ -85,8 +89,10 @@ def test_edges_chained_selection_with_edge_filter(graph): graph(path: "g") { edges { select(expr: { dst: { - field: NODE_ID - where: { eq: { u64: 2 } } + node: { + field: NODE_ID + where: { eq: { u64: 2 } } + } } }) { select(expr: { property: { name: "p2", where: { gt:{ i64: 2 } } } }) { list { src { name } dst { name } } diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_graph_edges_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_graph_edges_property_filter.py index 579f4b3016..20c7c0d86b 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_graph_edges_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_graph_edges_property_filter.py @@ -652,8 +652,10 @@ def test_edges_chained_selection_edges_filter_paired(graph): select(expr: { property: { name: "p2", where: { lt: { i64: 5 } } } }) { filter(expr: { dst: { - field: NODE_ID - where: { eq: { u64: 2 } } + node: { + field: NODE_ID + where: { eq: { u64: 2 } } + } } }) { list { src { name } dst { name } } @@ -685,8 +687,10 @@ def test_edges_chained_selection_edges_filter_paired_ver2(graph): select(expr: { property: { name: "p2", where: { lt: { i64: 5 } } } }) { filter(expr: { dst: { - field: NODE_ID - where: { eq: { u64: 2 } } + node: { + field: NODE_ID + where: { eq: { u64: 2 } } + } } }) { list { src { name } dst { name } } diff --git a/raphtory-api/src/core/entities/mod.rs b/raphtory-api/src/core/entities/mod.rs index c729655a64..d50e7e0350 100644 --- a/raphtory-api/src/core/entities/mod.rs +++ b/raphtory-api/src/core/entities/mod.rs @@ -226,7 +226,7 @@ impl GID { } } - pub fn to_str(&self) -> Cow { + pub fn to_str(&self) -> Cow<'_, str> { match self { GID::U64(v) => Cow::Owned(v.to_string()), GID::Str(v) => Cow::Borrowed(v), @@ -247,7 +247,7 @@ impl GID { } } - pub fn as_ref(&self) -> GidRef { + pub fn as_ref(&self) -> GidRef<'_> { match self { GID::U64(v) => GidRef::U64(*v), GID::Str(v) => GidRef::Str(v), @@ -455,7 +455,7 @@ impl LayerIds { } } - pub fn constrain_from_edge(&self, e: EdgeRef) -> Cow { + pub fn constrain_from_edge(&self, e: EdgeRef) -> Cow<'_, LayerIds> { match e.layer() { None => Cow::Borrowed(self), Some(l) => self diff --git a/raphtory-benchmark/benches/search_bench.rs b/raphtory-benchmark/benches/search_bench.rs index b9e6efea3d..6672d7d1be 100644 --- a/raphtory-benchmark/benches/search_bench.rs +++ b/raphtory-benchmark/benches/search_bench.rs @@ -10,19 +10,19 @@ use raphtory::{ properties::internal::{ InternalMetadataOps, InternalTemporalPropertiesOps, InternalTemporalPropertyViewOps, }, - view::{BaseFilterOps, SearchableGraphOps}, + view::{Filter, SearchableGraphOps}, }, graph::{ edge::EdgeView, node::NodeView, views::filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, + edge_filter::EdgeFilter, filter_operator::{FilterOperator, FilterOperator::*}, node_filter::{NodeFilter, NodeFilterBuilderOps}, property_filter::{ - InternalPropertyFilterOps, PropertyFilterBuilder, PropertyFilterOps, + InternalPropertyFilterBuilderOps, PropertyFilterBuilder, PropertyFilterOps, }, - ComposableFilter, PropertyFilterFactory, + ComposableFilter, InternalPropertyFilterFactory, PropertyFilterFactory, }, }, }, @@ -199,18 +199,17 @@ fn convert_to_property_filter( sampled_values: Option>, ) -> Option> where - M: PropertyFilterFactory + Default, - PropertyFilterBuilder: PropertyFilterOps + InternalPropertyFilterOps, + M: PropertyFilterFactory + Default + InternalPropertyFilterFactory, + ::PropertyBuilder: PropertyFilterOps + + InternalPropertyFilterBuilderOps>, { let mut rng = thread_rng(); match prop_value.dtype() { - // String properties support tokenized matches for eq and ne PropType::Str => { if let Some(full_str) = prop_value.into_str() { let tokens: Vec<&str> = full_str.split_whitespace().collect(); if tokens.len() > 1 && rng.gen_bool(0.3) { - // 30% chance to use a random substring let start = rng.gen_range(0..tokens.len()); let end = rng.gen_range(start..tokens.len()); let sub_str = tokens[start..=end].join(" "); @@ -223,7 +222,7 @@ where } IsNotIn => sampled_values .map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => None, // No numeric comparison for strings + _ => None, } } else { match filter_op { @@ -234,7 +233,7 @@ where } IsNotIn => sampled_values .map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => None, // No numeric comparison for strings + _ => None, } } } else { @@ -242,7 +241,6 @@ where } } - // Numeric properties support all comparison operators PropType::U64 => prop_value.into_u64().and_then(|v| match filter_op { Eq => Some(M::default().property(prop_name).eq(v)), Ne => Some(M::default().property(prop_name).ne(v)), @@ -252,8 +250,9 @@ where Ge => Some(M::default().property(prop_name).ge(v)), IsIn => sampled_values.map(|vals| M::default().property(prop_name).is_in(vals)), IsNotIn => sampled_values.map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => return None, + _ => None, }), + PropType::I64 => prop_value.into_i64().and_then(|v| match filter_op { Eq => Some(M::default().property(prop_name).eq(v)), Ne => Some(M::default().property(prop_name).ne(v)), @@ -263,8 +262,9 @@ where Ge => Some(M::default().property(prop_name).ge(v)), IsIn => sampled_values.map(|vals| M::default().property(prop_name).is_in(vals)), IsNotIn => sampled_values.map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => return None, + _ => None, }), + PropType::F64 => prop_value.into_f64().and_then(|v| match filter_op { Eq => Some(M::default().property(prop_name).eq(v)), Ne => Some(M::default().property(prop_name).ne(v)), @@ -274,17 +274,18 @@ where Ge => Some(M::default().property(prop_name).ge(v)), IsIn => sampled_values.map(|vals| M::default().property(prop_name).is_in(vals)), IsNotIn => sampled_values.map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => return None, + _ => None, }), + PropType::Bool => prop_value.into_bool().and_then(|v| match filter_op { Eq => Some(M::default().property(prop_name).eq(v)), Ne => Some(M::default().property(prop_name).ne(v)), IsIn => sampled_values.map(|vals| M::default().property(prop_name).is_in(vals)), IsNotIn => sampled_values.map(|vals| M::default().property(prop_name).is_not_in(vals)), - _ => return None, + _ => None, }), - _ => None, // Skip unsupported types + _ => None, } } diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index 383fcd2a8e..09d68b44b3 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -318,11 +318,11 @@ input EdgeFilter @oneOf { """ Source node filter. """ - src: NodeFieldFilterNew + src: NodeFilter """ Destination node filter. """ - dst: NodeFieldFilterNew + dst: NodeFilter """ Property filter. """ @@ -893,8 +893,8 @@ type Graph { } type GraphAlgorithmPlugin { - shortest_path(source: String!, targets: [String!]!, direction: String): [ShortestPathOutput!]! pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! + shortest_path(source: String!, targets: [String!]!, direction: String): [ShortestPathOutput!]! } type GraphSchema { diff --git a/raphtory-graphql/src/lib.rs b/raphtory-graphql/src/lib.rs index 55c99df880..cbadf3f83a 100644 --- a/raphtory-graphql/src/lib.rs +++ b/raphtory-graphql/src/lib.rs @@ -115,9 +115,10 @@ mod graphql_test { { property: { name: "p1", - operator: GREATER_THAN, - value: { - u64: 2 + where: { + gt: { + u64: 2 + } } } }, @@ -126,27 +127,30 @@ mod graphql_test { { node: { field: NODE_NAME, - operator: EQUAL, - value: { - str: "N1" + where: { + eq: { + str: "N1" + } } } }, { node: { field: NODE_TYPE, - operator: NOT_EQUAL, - value: { - str: "air_nomads" + where: { + ne: { + str: "air_nomads" + } } } }, { property: { name: "p1", - operator: LESS_THAN, - value: { - u64: 5 + where: { + lt: { + u64: 5 + } } } } @@ -166,6 +170,7 @@ mod graphql_test { "#; let req = Request::new(query); let res = schema.execute(req).await; + assert_eq!(res.errors, []); let mut data = res.data.into_json().unwrap(); if let Some(nodes) = data["graph"]["searchNodes"].as_array_mut() { diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index 5ae11ea44d..5946758677 100644 --- a/raphtory-graphql/src/model/graph/edge.rs +++ b/raphtory-graphql/src/model/graph/edge.rs @@ -13,7 +13,7 @@ use crate::{ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ db::{ - api::view::{BaseFilterOps, DynamicGraph, EdgeViewOps, IntoDynamic, StaticGraphViewOps}, + api::view::{DynamicGraph, EdgeViewOps, Filter, IntoDynamic, StaticGraphViewOps}, graph::{edge::EdgeView, views::filter::model::edge_filter::CompositeEdgeFilter}, }, errors::GraphError, diff --git a/raphtory-graphql/src/model/graph/edges.rs b/raphtory-graphql/src/model/graph/edges.rs index 84a2278d50..be2a25a12b 100644 --- a/raphtory-graphql/src/model/graph/edges.rs +++ b/raphtory-graphql/src/model/graph/edges.rs @@ -15,7 +15,7 @@ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use itertools::Itertools; use raphtory::{ db::{ - api::view::{internal::BaseFilter, DynamicGraph, IterFilterOps}, + api::view::{internal::InternalFilter, DynamicGraph, Select}, graph::{edges::Edges, views::filter::model::edge_filter::CompositeEdgeFilter}, }, errors::GraphError, @@ -25,7 +25,7 @@ use raphtory_api::iter::IntoDynBoxed; use std::{cmp::Ordering, sync::Arc}; use crate::model::graph::filtering::GqlEdgeFilter; -use raphtory::db::api::view::BaseFilterOps; +use raphtory::db::api::view::Filter; #[derive(ResolvedObject, Clone)] #[graphql(name = "Edges")] diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 6a95fc7aee..0c4c8c64f9 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -12,20 +12,16 @@ use raphtory::{ filter_operator::FilterOperator, node_filter::{CompositeNodeFilter, NodeFilter}, property_filter::{Op, PropertyFilter, PropertyFilterValue, PropertyRef}, - Filter, FilterValue, + Filter, FilterValue, Windowed, }, errors::GraphError, }; -use raphtory_api::core::{ - entities::{properties::prop::Prop, GID}, - storage::timeindex::TimeIndexEntry, -}; +use raphtory_api::core::entities::{properties::prop::Prop, GID}; use std::{ borrow::Cow, collections::HashSet, fmt, fmt::{Display, Formatter}, - marker::PhantomData, ops::Deref, sync::Arc, }; @@ -437,9 +433,9 @@ pub enum GqlNodeFilter { #[graphql(name = "EdgeFilter")] pub enum GqlEdgeFilter { /// Source node filter. - Src(NodeFieldFilterNew), + Src(GqlNodeFilter), /// Destination node filter. - Dst(NodeFieldFilterNew), + Dst(GqlNodeFilter), /// Property filter. Property(PropertyFilterNew), /// Metadata filter. @@ -872,21 +868,6 @@ fn build_property_filter_from_condition_with_entity( - prop_ref: PropertyRef, - cond: &PropCondition, - start: i64, - end: i64, -) -> Result>, GraphError> { - build_property_filter_from_condition_with_entity::< - raphtory::db::graph::views::filter::model::Windowed, - >( - prop_ref, - cond, - raphtory::db::graph::views::filter::model::Windowed::from_times(start, end), - ) -} - fn build_node_filter_from_prop_condition( prop_ref: PropertyRef, cond: &PropCondition, @@ -955,21 +936,12 @@ impl TryFrom for CompositeNodeFilter { GqlNodeFilter::TemporalProperty(prop) => { let prop_ref = PropertyRef::TemporalProperty(prop.name); if let Some(w) = prop.window { - let pf = build_windowed_property_filter_from_condition::( - prop_ref, - &prop.where_, - w.start, - w.end, - )?; - return Ok(CompositeNodeFilter::PropertyWindowed(pf)); + let filter = build_node_filter_from_prop_condition(prop_ref, &prop.where_)?; + let filter = Windowed::from_times(w.start, w.end, filter); + let filter = CompositeNodeFilter::Windowed(Box::new(filter)); + return Ok(filter); } - - let pf = build_property_filter_from_condition_with_entity::( - prop_ref, - &prop.where_, - NodeFilter, - )?; - Ok(CompositeNodeFilter::Property(pf)) + build_node_filter_from_prop_condition(prop_ref, &prop.where_) } GqlNodeFilter::And(and_filters) => { let mut iter = and_filters.into_iter().map(TryInto::try_into); @@ -1047,33 +1019,13 @@ impl TryFrom for CompositeEdgeFilter { type Error = GraphError; fn try_from(filter: GqlEdgeFilter) -> Result { match filter { - GqlEdgeFilter::Src(src) => { - if matches!(src.field, NodeField::NodeType) { - return Err(GraphError::InvalidGqlFilter( - "Src filter does not support NODE_TYPE".into(), - )); - } - let (_, field_value, operator) = - translate_node_field_where(src.field, &src.where_)?; - Ok(CompositeEdgeFilter::Edge(Filter { - field_name: "src".to_string(), - field_value, - operator, - })) + GqlEdgeFilter::Src(nf) => { + let nf: CompositeNodeFilter = nf.try_into()?; + Ok(CompositeEdgeFilter::Src(nf)) } - GqlEdgeFilter::Dst(dst) => { - if matches!(dst.field, NodeField::NodeType) { - return Err(GraphError::InvalidGqlFilter( - "Dst filter does not support NODE_TYPE".into(), - )); - } - let (_, field_value, operator) = - translate_node_field_where(dst.field, &dst.where_)?; - Ok(CompositeEdgeFilter::Edge(Filter { - field_name: "dst".to_string(), - field_value, - operator, - })) + GqlEdgeFilter::Dst(nf) => { + let nf: CompositeNodeFilter = nf.try_into()?; + Ok(CompositeEdgeFilter::Dst(nf)) } GqlEdgeFilter::Property(p) => { let prop_ref = PropertyRef::Property(p.name); @@ -1083,19 +1035,15 @@ impl TryFrom for CompositeEdgeFilter { let prop_ref = PropertyRef::Metadata(p.name); build_edge_filter_from_prop_condition(prop_ref, &p.where_) } - GqlEdgeFilter::TemporalProperty(p) => { - let prop_ref = PropertyRef::TemporalProperty(p.name); - if let Some(w) = p.window { - let pf = build_windowed_property_filter_from_condition::( - prop_ref, &p.where_, w.start, w.end, - )?; - return Ok(CompositeEdgeFilter::PropertyWindowed(pf)); + GqlEdgeFilter::TemporalProperty(prop) => { + let prop_ref = PropertyRef::TemporalProperty(prop.name); + if let Some(w) = prop.window { + let filter = build_edge_filter_from_prop_condition(prop_ref, &prop.where_)?; + let filter = Windowed::from_times(w.start, w.end, filter); + let filter = CompositeEdgeFilter::Windowed(Box::new(filter)); + return Ok(filter); } - - let pf = build_property_filter_from_condition_with_entity::( - prop_ref, &p.where_, EdgeFilter, - )?; - Ok(CompositeEdgeFilter::Property(pf)) + build_edge_filter_from_prop_condition(prop_ref, &prop.where_) } GqlEdgeFilter::And(and_filters) => { let mut iter = and_filters.into_iter().map(TryInto::try_into); diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index 4e49e1e309..83309e4521 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -28,8 +28,8 @@ use raphtory::{ api::{ properties::dyn_props::DynProperties, view::{ - BaseFilterOps, DynamicGraph, IntoDynamic, IterFilterOps, NodeViewOps, - SearchableGraphOps, StaticGraphViewOps, TimeOps, + DynamicGraph, Filter, IntoDynamic, NodeViewOps, SearchableGraphOps, Select, + StaticGraphViewOps, TimeOps, }, }, graph::{ @@ -384,7 +384,7 @@ impl GqlGraph { move || nn_clone.select(nf) }) .await?; - return Ok(GqlNodes::new(narrowed.into_dyn())); + return Ok(GqlNodes::new(narrowed)); } Ok(GqlNodes::new(nn)) diff --git a/raphtory-graphql/src/model/graph/node.rs b/raphtory-graphql/src/model/graph/node.rs index 0203796cf8..0952be5689 100644 --- a/raphtory-graphql/src/model/graph/node.rs +++ b/raphtory-graphql/src/model/graph/node.rs @@ -17,7 +17,7 @@ use raphtory::{ db::{ api::{ properties::dyn_props::DynProperties, - view::{BaseFilterOps, *}, + view::{Filter, *}, }, graph::{ node::NodeView, diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index 4efaf45bd3..8b4a01606d 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -16,10 +16,13 @@ use itertools::Itertools; use raphtory::{ db::{ api::{ - state::Index, - view::{BaseFilterOps, DynamicGraph, IterFilterOps}, + state::{ops::DynNodeFilter, Index}, + view::{DynamicGraph, Filter, Select}, + }, + graph::{ + nodes::{IntoDynNodes, Nodes}, + views::filter::model::node_filter::CompositeNodeFilter, }, - graph::{nodes::Nodes, views::filter::model::node_filter::CompositeNodeFilter}, }, errors::GraphError, prelude::*, @@ -30,18 +33,20 @@ use std::cmp::Ordering; #[derive(ResolvedObject, Clone)] #[graphql(name = "Nodes")] pub(crate) struct GqlNodes { - pub(crate) nn: Nodes<'static, DynamicGraph>, + pub(crate) nn: Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>, } impl GqlNodes { - fn update>>(&self, nodes: N) -> Self { + fn update(&self, nodes: N) -> Self { GqlNodes::new(nodes) } } impl GqlNodes { - pub(crate) fn new>>(nodes: N) -> Self { - Self { nn: nodes.into() } + pub(crate) fn new(nodes: N) -> Self { + Self { + nn: nodes.into_dyn(), + } } fn iter(&self) -> Box + '_> { diff --git a/raphtory-graphql/src/model/graph/path_from_node.rs b/raphtory-graphql/src/model/graph/path_from_node.rs index 153ccf5484..eca9ece0bb 100644 --- a/raphtory-graphql/src/model/graph/path_from_node.rs +++ b/raphtory-graphql/src/model/graph/path_from_node.rs @@ -10,7 +10,7 @@ use crate::{ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ db::{ - api::view::{BaseFilterOps, DynamicGraph, IterFilterOps}, + api::view::{DynamicGraph, Filter, Select}, graph::{path::PathFromNode, views::filter::model::node_filter::CompositeNodeFilter}, }, errors::GraphError, diff --git a/raphtory-graphql/src/model/graph/windowset.rs b/raphtory-graphql/src/model/graph/windowset.rs index 742b3f8f10..32b1501029 100644 --- a/raphtory-graphql/src/model/graph/windowset.rs +++ b/raphtory-graphql/src/model/graph/windowset.rs @@ -8,7 +8,10 @@ use crate::{ }; use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::db::{ - api::view::{DynamicGraph, WindowSet}, + api::{ + state::ops::DynNodeFilter, + view::{DynamicGraph, WindowSet}, + }, graph::{edge::EdgeView, edges::Edges, node::NodeView, nodes::Nodes, path::PathFromNode}, }; @@ -122,11 +125,13 @@ impl GqlNodeWindowSet { #[derive(ResolvedObject, Clone)] #[graphql(name = "NodesWindowSet")] pub(crate) struct GqlNodesWindowSet { - pub(crate) ws: WindowSet<'static, Nodes<'static, DynamicGraph, DynamicGraph>>, + pub(crate) ws: WindowSet<'static, Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>>, } impl GqlNodesWindowSet { - pub(crate) fn new(ws: WindowSet<'static, Nodes>) -> Self { + pub(crate) fn new( + ws: WindowSet<'static, Nodes>, + ) -> Self { Self { ws } } } diff --git a/raphtory-graphql/src/model/schema/node_schema.rs b/raphtory-graphql/src/model/schema/node_schema.rs index 0c4ac26f30..193b262b93 100644 --- a/raphtory-graphql/src/model/schema/node_schema.rs +++ b/raphtory-graphql/src/model/schema/node_schema.rs @@ -2,8 +2,11 @@ use crate::model::schema::{property_schema::PropertySchema, DEFAULT_NODE_TYPE}; use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ db::{ - api::view::DynamicGraph, - graph::views::filter::node_type_filtered_graph::NodeTypeFilteredGraph, + api::{ + state::ops::{filter::MaskOp, TypeId}, + view::DynamicGraph, + }, + graph::views::filter::node_filtered_graph::NodeFilteredGraph, }, prelude::*, }; @@ -80,8 +83,9 @@ impl NodeSchema { let mut node_types_filter = vec![false; self.graph.node_meta().node_type_meta().len()]; node_types_filter[self.type_id] = true; + let filter = TypeId.mask(node_types_filter.into()); let unique_values: ahash::HashSet<_> = - NodeTypeFilteredGraph::new(self.graph.clone(), node_types_filter.into()) + NodeFilteredGraph::new(self.graph.clone(), filter) .nodes() .properties() .into_iter_values() @@ -134,8 +138,9 @@ impl NodeSchema { let mut node_types_filter = vec![false; self.graph.node_meta().node_type_meta().len()]; node_types_filter[self.type_id] = true; + let filter = TypeId.mask(node_types_filter.into()); let unique_values: ahash::HashSet<_> = - NodeTypeFilteredGraph::new(self.graph.clone(), node_types_filter.into()) + NodeFilteredGraph::new(self.graph.clone(), filter) .nodes() .metadata() .into_iter_values() diff --git a/raphtory/src/algorithms/components/in_components.rs b/raphtory/src/algorithms/components/in_components.rs index bc1860407f..1a17822042 100644 --- a/raphtory/src/algorithms/components/in_components.rs +++ b/raphtory/src/algorithms/components/in_components.rs @@ -2,7 +2,7 @@ use crate::{ core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ api::{ - state::{Index, NodeState}, + state::{ops::Const, Index, NodeState}, view::{NodeViewOps, StaticGraphViewOps}, }, graph::{node::NodeView, nodes::Nodes}, @@ -76,8 +76,8 @@ where Nodes::new_filtered( g.clone(), g.clone(), + Const(true), Some(Index::from_iter(v.in_components)), - None, ) }) }, @@ -124,7 +124,6 @@ pub fn in_component<'graph, G: GraphViewOps<'graph>>( let (nodes, distances): (IndexSet<_, ahash::RandomState>, Vec<_>) = in_components.into_iter().sorted().unzip(); NodeState::new( - node.graph.clone(), node.graph.clone(), distances.into(), Some(Index::new(nodes)), diff --git a/raphtory/src/algorithms/components/out_components.rs b/raphtory/src/algorithms/components/out_components.rs index c930da3f8d..0fb29a905a 100644 --- a/raphtory/src/algorithms/components/out_components.rs +++ b/raphtory/src/algorithms/components/out_components.rs @@ -2,7 +2,7 @@ use crate::{ core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ api::{ - state::{Index, NodeState}, + state::{ops::Const, Index, NodeState}, view::{NodeViewOps, StaticGraphViewOps}, }, graph::{node::NodeView, nodes::Nodes}, @@ -76,8 +76,8 @@ where Nodes::new_filtered( g.clone(), g.clone(), + Const(true), Some(Index::from_iter(v.out_components)), - None, ) }) }, @@ -124,7 +124,6 @@ pub fn out_component<'graph, G: GraphViewOps<'graph>>( let (nodes, distances): (IndexSet<_, ahash::RandomState>, Vec<_>) = out_components.into_iter().sorted().unzip(); NodeState::new( - node.graph.clone(), node.graph.clone(), distances.into(), Some(Index::new(nodes)), diff --git a/raphtory/src/algorithms/dynamics/temporal/epidemics.rs b/raphtory/src/algorithms/dynamics/temporal/epidemics.rs index 4ce237cdd1..bc5a1b3b2c 100644 --- a/raphtory/src/algorithms/dynamics/temporal/epidemics.rs +++ b/raphtory/src/algorithms/dynamics/temporal/epidemics.rs @@ -245,7 +245,6 @@ where } let (index, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = states.into_iter().unzip(); Ok(NodeState::new( - g.clone(), g.clone(), values.into(), Some(Index::new(index)), diff --git a/raphtory/src/algorithms/metrics/clustering_coefficient/local_clustering_coefficient_batch.rs b/raphtory/src/algorithms/metrics/clustering_coefficient/local_clustering_coefficient_batch.rs index 3eb574ff1d..3b0594518c 100644 --- a/raphtory/src/algorithms/metrics/clustering_coefficient/local_clustering_coefficient_batch.rs +++ b/raphtory/src/algorithms/metrics/clustering_coefficient/local_clustering_coefficient_batch.rs @@ -48,7 +48,7 @@ pub fn local_clustering_coefficient_batch( }) .unzip(); let result: Option<_> = Some(Index::new(index)); - NodeState::new(graph.clone(), graph.clone(), values.into(), result) + NodeState::new(graph.clone(), values.into(), result) } #[cfg(test)] diff --git a/raphtory/src/algorithms/pathing/dijkstra.rs b/raphtory/src/algorithms/pathing/dijkstra.rs index b13008a354..94ce147b10 100644 --- a/raphtory/src/algorithms/pathing/dijkstra.rs +++ b/raphtory/src/algorithms/pathing/dijkstra.rs @@ -3,7 +3,7 @@ use crate::{core::entities::nodes::node_ref::AsNodeRef, db::api::view::StaticGra use crate::{ core::entities::nodes::node_ref::NodeRef, db::{ - api::state::{Index, NodeState}, + api::state::{ops::filter::NO_FILTER, Index, NodeState}, graph::nodes::Nodes, }, errors::GraphError, @@ -189,12 +189,12 @@ pub fn dijkstra_single_source_shortest_paths, Vec<_>) = paths .into_iter() .map(|(id, (cost, path))| { - let nodes = Nodes::new_filtered(g.clone(), g.clone(), Some(Index::new(path)), None); + let nodes = + Nodes::new_filtered(g.clone(), g.clone(), NO_FILTER, Some(Index::new(path))); (id, (cost, nodes)) }) .unzip(); Ok(NodeState::new( - g.clone(), g.clone(), values.into(), Some(Index::new(index)), diff --git a/raphtory/src/algorithms/pathing/single_source_shortest_path.rs b/raphtory/src/algorithms/pathing/single_source_shortest_path.rs index b97ba80586..66b2708b58 100644 --- a/raphtory/src/algorithms/pathing/single_source_shortest_path.rs +++ b/raphtory/src/algorithms/pathing/single_source_shortest_path.rs @@ -5,7 +5,7 @@ use crate::{ core::entities::{nodes::node_ref::AsNodeRef, VID}, db::{ - api::state::{Index, NodeState}, + api::state::{ops::filter::NO_FILTER, Index, NodeState}, graph::{node::NodeView, nodes::Nodes}, }, prelude::*, @@ -58,7 +58,7 @@ pub fn single_source_shortest_path<'graph, G: GraphViewOps<'graph>, T: AsNodeRef } } NodeState::new_from_map(g.clone(), paths, |v| { - Nodes::new_filtered(g.clone(), g.clone(), Some(Index::from_iter(v)), None) + Nodes::new_filtered(g.clone(), g.clone(), NO_FILTER, Some(Index::from_iter(v))) }) } diff --git a/raphtory/src/db/api/state/group_by.rs b/raphtory/src/db/api/state/group_by.rs index a59ceabd92..00b6aed178 100644 --- a/raphtory/src/db/api/state/group_by.rs +++ b/raphtory/src/db/api/state/group_by.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::state::Index, + api::state::{ops::Const, Index}, graph::{nodes::Nodes, views::node_subgraph::NodeSubgraph}, }, prelude::{GraphViewOps, NodeStateOps}, @@ -40,8 +40,8 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr Nodes::new_filtered( self.graph.clone(), self.graph.clone(), + Const(true), Some(nodes.clone()), - None, ), ) }) @@ -86,8 +86,8 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr Nodes::new_filtered( self.graph.clone(), self.graph.clone(), + Const(true), Some(nodes.clone()), - None, ), ) }) diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 3249677a66..93e0ce5699 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -2,13 +2,16 @@ use crate::{ core::entities::{nodes::node_ref::AsNodeRef, VID}, db::{ api::{ - state::{ops::node::NodeOp, Index, NodeState, NodeStateOps}, - view::{ - internal::{FilterOps, NodeList}, - BoxedLIter, IntoDynBoxed, + state::{ + ops::{Const, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, NodeOp}, + Index, NodeState, NodeStateOps, }, + view::{internal::NodeList, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic}, + }, + graph::{ + node::NodeView, + nodes::{IntoDynNodes, Nodes}, }, - graph::{node::NodeView, nodes::Nodes}, }, prelude::*, }; @@ -20,15 +23,21 @@ use std::{ }; #[derive(Clone)] -pub struct LazyNodeState<'graph, Op, G, GH = G> { - nodes: Nodes<'graph, G, GH>, +pub struct LazyNodeState<'graph, Op, G, GH = G, F = Const> { + nodes: Nodes<'graph, G, GH, F>, pub(crate) op: Op, } -impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, RHS> - PartialEq<&[RHS]> for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + O: NodeOp + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + RHS, + > PartialEq<&[RHS]> for LazyNodeState<'graph, O, G, GH, F> where - Op::Output: PartialEq, + O::Output: PartialEq, { fn eq(&self, other: &&[RHS]) -> bool { self.len() == other.len() && self.iter_values().zip(other.iter()).all(|(a, b)| a == *b) @@ -37,14 +46,15 @@ where impl< 'graph, - Op: NodeOp + 'graph, + O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, RHS, const N: usize, - > PartialEq<[RHS; N]> for LazyNodeState<'graph, Op, G, GH> + > PartialEq<[RHS; N]> for LazyNodeState<'graph, O, G, GH, F> where - Op::Output: PartialEq, + O::Output: PartialEq, { fn eq(&self, other: &[RHS; N]) -> bool { self.len() == other.len() && self.iter_values().zip(other.iter()).all(|(a, b)| a == *b) @@ -53,13 +63,14 @@ where impl< 'graph, - Op: NodeOp + 'graph, + O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, - RHS: NodeStateOps<'graph, OwnedValue = Op::Output>, - > PartialEq for LazyNodeState<'graph, Op, G, GH> + F: NodeFilterOp + Clone + 'graph, + RHS: NodeStateOps<'graph, OwnedValue = O::Output>, + > PartialEq for LazyNodeState<'graph, O, G, GH, F> where - Op::Output: PartialEq, + O::Output: PartialEq, { fn eq(&self, other: &RHS) -> bool { self.len() == other.len() @@ -72,30 +83,46 @@ where } } -impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, RHS> - PartialEq> for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + O: NodeOp + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + RHS, + > PartialEq> for LazyNodeState<'graph, O, G, GH, F> where - Op::Output: PartialEq, + O::Output: PartialEq, { fn eq(&self, other: &Vec) -> bool { self.len() == other.len() && self.iter_values().zip(other.iter()).all(|(a, b)| a == *b) } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, Op: NodeOp + 'graph> Debug - for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + 'graph, + O: NodeOp + 'graph, + > Debug for LazyNodeState<'graph, O, G, GH, F> where - Op::Output: Debug, + O::Output: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter_values()).finish() } } -impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> IntoIterator - for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + O: NodeOp + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + > IntoIterator for LazyNodeState<'graph, O, G, GH, F> { - type Item = (NodeView<'graph, G>, Op::Output); + type Item = (NodeView<'graph, GH>, O::Output); type IntoIter = BoxedLIter<'graph, Self::Item>; fn into_iter(self) -> Self::IntoIter { @@ -107,63 +134,75 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra } } -impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> - LazyNodeState<'graph, Op, G, GH> +impl + LazyNodeState<'static, O, G, GH, F> { - pub(crate) fn new(op: Op, nodes: Nodes<'graph, G, GH>) -> Self { + pub fn into_dyn(self) -> LazyNodeState<'static, O, DynamicGraph, DynamicGraph, DynNodeFilter> { + LazyNodeState { + nodes: self.nodes.into_dyn(), + op: self.op, + } + } +} + +impl< + 'graph, + O: NodeOp + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + > LazyNodeState<'graph, O, G, GH, F> +{ + pub(crate) fn new(op: O, nodes: Nodes<'graph, G, GH, F>) -> Self { Self { nodes, op } } - pub fn collect>(&self) -> C { + pub fn collect>(&self) -> C { self.par_iter_values().collect() } - pub fn collect_vec(&self) -> Vec { + pub fn collect_vec(&self) -> Vec { self.collect() } - pub fn compute(&self) -> NodeState<'graph, Op::Output, G, GH> { + pub fn compute(&self) -> NodeState<'graph, O::Output, GH> { if self.nodes.is_filtered() { let (keys, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = self .par_iter() .map(|(node, value)| (node.node, value)) .unzip(); NodeState::new( - self.nodes.base_graph.clone(), - self.nodes.iter_graph.clone(), + self.nodes.graph.clone(), values.into(), Some(Index::new(keys)), ) } else { let values = self.collect_vec(); - NodeState::new( - self.nodes.base_graph.clone(), - self.nodes.iter_graph.clone(), - values.into(), - None, - ) + NodeState::new(self.nodes.graph.clone(), values.into(), None) } } } -impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> - NodeStateOps<'graph> for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + O: NodeOp + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + 'graph, + > NodeStateOps<'graph> for LazyNodeState<'graph, O, G, GH, F> { - type Graph = GH; + type Select = F; type BaseGraph = G; + type Graph = GH; type Value<'a> - = Op::Output + = O::Output where 'graph: 'a, Self: 'a; - type OwnedValue = Op::Output; + type OwnedValue = O::Output; fn graph(&self) -> &Self::Graph { - &self.nodes.iter_graph - } - - fn base_graph(&self) -> &Self::BaseGraph { - &self.nodes.base_graph + &self.nodes.graph } fn iter_values<'a>(&'a self) -> impl Iterator> + 'a @@ -202,7 +241,7 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra fn iter<'a>( &'a self, - ) -> impl Iterator, Self::Value<'a>)> + 'a + ) -> impl Iterator, Self::Value<'a>)> + 'a where 'graph: 'a, { @@ -212,13 +251,13 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra .map(move |node| (node, self.op.apply(&storage, node.node))) } - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph> { + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph, Self::Select> { self.nodes.clone() } fn par_iter<'a>( &'a self, - ) -> impl ParallelIterator, Self::Value<'a>)> + ) -> impl ParallelIterator, Self::Value<'a>)> where 'graph: 'a, { @@ -228,11 +267,11 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra .map(move |node| (node, self.op.apply(&storage, node.node))) } - fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { - if self.graph().filtered() { + fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { + if self.nodes().is_list_filtered() { self.iter().nth(index) } else { - let vid = match self.graph().node_list() { + let vid = match self.nodes().node_list() { NodeList::All { len } => { if index < len { VID(index) @@ -244,7 +283,7 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra }; let cg = self.graph().core_graph(); Some(( - NodeView::new_internal(self.base_graph(), vid), + NodeView::new_internal(self.graph(), vid), self.op.apply(cg, vid), )) } @@ -266,7 +305,7 @@ mod test { db::api::{ state::{ lazy_node_state::LazyNodeState, - ops::node::{Degree, NodeOp}, + ops::{node::NodeOp, Degree}, }, view::IntoDynamic, }, @@ -289,7 +328,7 @@ mod test { let g_dyn = g.clone().into_dynamic(); let deg = Degree { - graph: g_dyn, + view: g_dyn, dir: Direction::BOTH, }; let arc_deg: Arc> = Arc::new(deg); diff --git a/raphtory/src/db/api/state/mod.rs b/raphtory/src/db/api/state/mod.rs index d76c2fafb3..69e1382f2a 100644 --- a/raphtory/src/db/api/state/mod.rs +++ b/raphtory/src/db/api/state/mod.rs @@ -3,7 +3,7 @@ mod lazy_node_state; mod node_state; mod node_state_ops; mod node_state_ord_ops; -pub(crate) mod ops; +pub mod ops; pub use group_by::{NodeGroups, NodeStateGroupBy}; pub use lazy_node_state::LazyNodeState; diff --git a/raphtory/src/db/api/state/node_state.rs b/raphtory/src/db/api/state/node_state.rs index 8b65748c91..22db08b750 100644 --- a/raphtory/src/db/api/state/node_state.rs +++ b/raphtory/src/db/api/state/node_state.rs @@ -2,7 +2,7 @@ use crate::{ core::entities::{nodes::node_ref::AsNodeRef, VID}, db::{ api::{ - state::node_state_ops::NodeStateOps, + state::{node_state_ops::NodeStateOps, ops::Const}, view::{ internal::{FilterOps, NodeList}, DynamicGraph, IntoDynBoxed, IntoDynamic, @@ -116,20 +116,15 @@ impl + From + Send + Sync> Index { } #[derive(Clone)] -pub struct NodeState<'graph, V, G, GH = G> { +pub struct NodeState<'graph, V, G> { base_graph: G, - graph: GH, values: Arc<[V]>, keys: Option>, _marker: PhantomData<&'graph ()>, } -impl< - 'graph, - V: Debug + Clone + Send + Sync + 'graph, - G: GraphViewOps<'graph>, - GH: Debug + GraphViewOps<'graph>, - > Debug for NodeState<'graph, V, G, GH> +impl<'graph, V: Debug + Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> Debug + for NodeState<'graph, V, G> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_map() @@ -138,16 +133,16 @@ impl< } } -impl<'graph, RHS: Send + Sync, V: PartialEq + Send + Sync + Clone + 'graph, G, GH> - PartialEq> for NodeState<'graph, V, G, GH> +impl<'graph, RHS: Send + Sync, V: PartialEq + Send + Sync + Clone + 'graph, G> + PartialEq> for NodeState<'graph, V, G> { fn eq(&self, other: &Vec) -> bool { self.values.par_iter().eq(other) } } -impl<'graph, RHS: Send + Sync, V: PartialEq + Send + Sync + Clone + 'graph, G, GH> - PartialEq<&[RHS]> for NodeState<'graph, V, G, GH> +impl<'graph, RHS: Send + Sync, V: PartialEq + Send + Sync + Clone + 'graph, G> + PartialEq<&[RHS]> for NodeState<'graph, V, G> { fn eq(&self, other: &&[RHS]) -> bool { self.values.par_iter().eq(*other) @@ -158,9 +153,8 @@ impl< 'graph, V: Clone + Send + Sync + PartialEq + 'graph, G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, RHS: NodeStateOps<'graph, OwnedValue = V>, - > PartialEq for NodeState<'graph, V, G, GH> + > PartialEq for NodeState<'graph, V, G> { fn eq(&self, other: &RHS) -> bool { self.len() == other.len() @@ -179,9 +173,8 @@ impl< RHS: Send + Sync, V: PartialEq + Send + Sync + Clone + 'graph, G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, S, - > PartialEq> for NodeState<'graph, V, G, GH> + > PartialEq> for NodeState<'graph, V, G> { fn eq(&self, other: &HashMap) -> bool { other.len() == self.len() @@ -191,14 +184,9 @@ impl< } } -impl<'graph, V, G: IntoDynamic, GH: IntoDynamic> NodeState<'graph, V, G, GH> { +impl<'graph, V, G: IntoDynamic> NodeState<'graph, V, G> { pub fn into_dyn(self) -> NodeState<'graph, V, DynamicGraph> { - NodeState::new( - self.base_graph.into_dynamic(), - self.graph.into_dynamic(), - self.values, - self.keys, - ) + NodeState::new(self.base_graph.into_dynamic(), self.values, self.keys) } } @@ -220,7 +208,7 @@ impl<'graph, V, G: GraphViewOps<'graph>> NodeState<'graph, V, G> { .map(|vid| values[vid.index()].clone()) .collect(), }; - Self::new(graph.clone(), graph, values.into(), index) + Self::new(graph, values.into(), index) } /// Construct a node state from an eval result, mapping values @@ -238,19 +226,19 @@ impl<'graph, V, G: GraphViewOps<'graph>> NodeState<'graph, V, G> { .map(|vid| map(values[vid.index()].clone())) .collect(), }; - Self::new(graph.clone(), graph, values, index) + Self::new(graph, values, index) } /// create a new empty NodeState pub fn new_empty(graph: G) -> Self { - Self::new(graph.clone(), graph, [].into(), Some(Index::default())) + Self::new(graph, [].into(), Some(Index::default())) } /// create a new NodeState from a list of values for the node (takes care of creating an index for /// node filtering when needed) pub fn new_from_values(graph: G, values: impl Into>) -> Self { let index = Index::for_graph(&graph); - Self::new(graph.clone(), graph, values.into(), index) + Self::new(graph, values.into(), index) } /// create a new NodeState from a HashMap of values @@ -272,16 +260,15 @@ impl<'graph, V, G: GraphViewOps<'graph>> NodeState<'graph, V, G> { .iter() .flat_map(|node| Some((node.node, map(values.remove(&node.node)?)))) .unzip(); - Self::new(graph.clone(), graph, values.into(), Some(Index::new(index))) + Self::new(graph, values.into(), Some(Index::new(index))) } } } -impl<'graph, V, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodeState<'graph, V, G, GH> { - pub fn new(base_graph: G, graph: GH, values: Arc<[V]>, keys: Option>) -> Self { +impl<'graph, V, G: GraphViewOps<'graph>> NodeState<'graph, V, G> { + pub fn new(base_graph: G, values: Arc<[V]>, keys: Option>) -> Self { Self { base_graph, - graph, values, keys, _marker: PhantomData, @@ -297,12 +284,8 @@ impl<'graph, V, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodeState<'gr } } -impl< - 'graph, - V: Send + Sync + Clone + 'graph, - G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, - > IntoIterator for NodeState<'graph, V, G, GH> +impl<'graph, V: Send + Sync + Clone + 'graph, G: GraphViewOps<'graph>> IntoIterator + for NodeState<'graph, V, G> { type Item = (NodeView<'graph, G>, V); type IntoIter = Box + 'graph>; @@ -316,15 +299,12 @@ impl< } } -impl< - 'graph, - V: Clone + Send + Sync + 'graph, - G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, - > NodeStateOps<'graph> for NodeState<'graph, V, G, GH> +impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeStateOps<'graph> + for NodeState<'graph, V, G> { type BaseGraph = G; - type Graph = GH; + type Graph = G; + type Select = Const; type Value<'a> = &'a V where @@ -332,10 +312,6 @@ impl< type OwnedValue = V; fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn base_graph(&self) -> &Self::BaseGraph { &self.base_graph } @@ -365,7 +341,7 @@ impl< fn iter<'a>( &'a self, - ) -> impl Iterator, Self::Value<'a>)> + 'a + ) -> impl Iterator, Self::Value<'a>)> + 'a where 'graph: 'a, { @@ -384,12 +360,12 @@ impl< } } - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph> { + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph, Self::Select> { Nodes::new_filtered( self.base_graph.clone(), - self.graph.clone(), + self.base_graph.clone(), + Const(true), self.keys.clone(), - None, ) } @@ -397,7 +373,7 @@ impl< &'a self, ) -> impl ParallelIterator< Item = ( - NodeView<'a, &'a >::BaseGraph>, + NodeView<'a, &'a >::Graph>, >::Value<'a>, ), > @@ -420,7 +396,7 @@ impl< } } - fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { match &self.keys { Some(node_index) => node_index.key(index).map(|n| { ( @@ -436,7 +412,7 @@ impl< } fn get_by_node(&self, node: N) -> Option> { - let id = self.graph.internalise_node(node.as_node_ref())?; + let id = self.base_graph.internalise_node(node.as_node_ref())?; match &self.keys { Some(index) => index.index(&id).map(|i| &self.values[i]), None => Some(&self.values[id.0]), @@ -461,7 +437,6 @@ mod test { g.add_node(0, 0, NO_PROPS, None).unwrap(); let float_state = NodeState { base_graph: g.clone(), - graph: g.clone(), values: [0.0f64].into(), keys: None, _marker: Default::default(), @@ -469,7 +444,6 @@ mod test { let int_state = NodeState { base_graph: g.clone(), - graph: g.clone(), values: [1i64].into(), keys: None, _marker: Default::default(), diff --git a/raphtory/src/db/api/state/node_state_ops.rs b/raphtory/src/db/api/state/node_state_ops.rs index a0f41e8f2b..26d3603bee 100644 --- a/raphtory/src/db/api/state/node_state_ops.rs +++ b/raphtory/src/db/api/state/node_state_ops.rs @@ -1,7 +1,10 @@ use crate::{ core::entities::nodes::node_ref::AsNodeRef, db::{ - api::state::{group_by::NodeGroups, node_state::NodeState, node_state_ord_ops, Index}, + api::state::{ + group_by::NodeGroups, node_state::NodeState, node_state_ord_ops, ops::NodeFilterOp, + Index, + }, graph::{node::NodeView, nodes::Nodes}, }, prelude::{GraphViewOps, NodeViewOps}, @@ -12,10 +15,11 @@ use rayon::prelude::*; use std::{borrow::Borrow, fmt::Debug, hash::Hash, iter::Sum}; pub trait NodeStateOps<'graph>: - IntoIterator, Self::OwnedValue)> + Send + Sync + 'graph + IntoIterator, Self::OwnedValue)> + Send + Sync + 'graph { type BaseGraph: GraphViewOps<'graph>; type Graph: GraphViewOps<'graph>; + type Select: NodeFilterOp; type Value<'a>: Send + Sync + Borrow where 'graph: 'a, @@ -25,8 +29,6 @@ pub trait NodeStateOps<'graph>: fn graph(&self) -> &Self::Graph; - fn base_graph(&self) -> &Self::BaseGraph; - fn iter_values<'a>(&'a self) -> impl Iterator> + 'a where 'graph: 'a; @@ -40,19 +42,19 @@ pub trait NodeStateOps<'graph>: fn iter<'a>( &'a self, - ) -> impl Iterator, Self::Value<'a>)> + 'a + ) -> impl Iterator, Self::Value<'a>)> + 'a where 'graph: 'a; - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph>; + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph, Self::Select>; fn par_iter<'a>( &'a self, - ) -> impl ParallelIterator, Self::Value<'a>)> + ) -> impl ParallelIterator, Self::Value<'a>)> where 'graph: 'a; - fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn get_by_node(&self, node: N) -> Option>; @@ -60,19 +62,19 @@ pub trait NodeStateOps<'graph>: fn sort_by< F: Fn( - (NodeView<&Self::BaseGraph>, &Self::OwnedValue), - (NodeView<&Self::BaseGraph>, &Self::OwnedValue), + (NodeView<&Self::Graph>, &Self::OwnedValue), + (NodeView<&Self::Graph>, &Self::OwnedValue), ) -> std::cmp::Ordering + Sync, >( &self, cmp: F, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { let mut state: Vec<_> = self .par_iter() .map(|(n, v)| (n.node, v.borrow().clone())) .collect(); - let base_graph = self.base_graph(); + let base_graph = self.graph(); state.par_sort_by(|(n1, v1), (n2, v2)| { cmp( (NodeView::new_internal(base_graph, *n1), v1), @@ -83,12 +85,7 @@ pub trait NodeStateOps<'graph>: let (keys, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = state.into_par_iter().unzip(); - NodeState::new( - self.base_graph().clone(), - self.graph().clone(), - values.into(), - Some(Index::new(keys)), - ) + NodeState::new(self.graph().clone(), values.into(), Some(Index::new(keys))) } /// Sorts the by its values in ascending or descending order. @@ -105,12 +102,12 @@ pub trait NodeStateOps<'graph>: >( &self, cmp: F, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.sort_by(|(_, v1), (_, v2)| cmp(v1, v2)) } /// Sort the results by global node id - fn sort_by_id(&self) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn sort_by_id(&self) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.sort_by(|(n1, _), (n2, _)| n1.id().cmp(&n2.id())) } @@ -132,7 +129,7 @@ pub trait NodeStateOps<'graph>: &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { let values = node_state_ord_ops::top_k( self.iter(), |(_, v1), (_, v2)| cmp(v1.borrow(), v2.borrow()), @@ -143,26 +140,21 @@ pub trait NodeStateOps<'graph>: .map(|(n, v)| (n.node, v.borrow().clone())) .unzip(); - NodeState::new( - self.base_graph().clone(), - self.graph().clone(), - values.into(), - Some(Index::new(keys)), - ) + NodeState::new(self.graph().clone(), values.into(), Some(Index::new(keys))) } fn bottom_k_by std::cmp::Ordering + Sync>( &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.top_k_by(|v1, v2| cmp(v1, v2).reverse(), k) } fn min_item_by std::cmp::Ordering + Sync>( &self, cmp: F, - ) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + ) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.par_iter() .min_by(|(_, v1), (_, v2)| cmp(v1.borrow(), v2.borrow())) } @@ -170,7 +162,7 @@ pub trait NodeStateOps<'graph>: fn max_item_by std::cmp::Ordering + Sync>( &self, cmp: F, - ) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + ) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.par_iter() .max_by(|(_, v1), (_, v2)| cmp(v1.borrow(), v2.borrow())) } @@ -178,7 +170,7 @@ pub trait NodeStateOps<'graph>: fn median_item_by std::cmp::Ordering + Sync>( &self, cmp: F, - ) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + ) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { let mut values: Vec<_> = self.par_iter().collect(); let len = values.len(); if len == 0 { diff --git a/raphtory/src/db/api/state/node_state_ord_ops.rs b/raphtory/src/db/api/state/node_state_ord_ops.rs index 9ef65c4b81..8f36547b78 100644 --- a/raphtory/src/db/api/state/node_state_ord_ops.rs +++ b/raphtory/src/db/api/state/node_state_ord_ops.rs @@ -58,10 +58,7 @@ where /// Returns: /// /// A sorted vector of tuples containing keys of type `H` and values of type `Y`. - fn sort_by_values( - &self, - reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn sort_by_values(&self, reverse: bool) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -77,29 +74,26 @@ where /// If `percentage` is `true`, the returned vector contains the top `k` percentage of elements. /// If `percentage` is `false`, the returned vector contains the top `k` elements. /// Returns empty vec if the result is empty or if `k` is 0. - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; /// Returns a tuple of the min result with its key - fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn min_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn min(&self) -> Option> { self.min_item().map(|(_, v)| v) } /// Returns a tuple of the max result with its key - fn max_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn max_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn max(&self) -> Option> { self.max_item().map(|(_, v)| v) } /// Returns a tuple of the median result with its key - fn median_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn median_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn median(&self) -> Option> { self.median_item().map(|(_, v)| v) @@ -116,10 +110,7 @@ pub trait AsOrderedNodeStateOps<'graph>: NodeStateOps<'graph> { /// Returns: /// /// A sorted vector of tuples containing keys of type `H` and values of type `Y`. - fn sort_by_values( - &self, - reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn sort_by_values(&self, reverse: bool) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -135,29 +126,26 @@ pub trait AsOrderedNodeStateOps<'graph>: NodeStateOps<'graph> { /// If `percentage` is `true`, the returned vector contains the top `k` percentage of elements. /// If `percentage` is `false`, the returned vector contains the top `k` elements. /// Returns empty vec if the result is empty or if `k` is 0. - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; /// Returns a tuple of the min result with its key - fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn min_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn min(&self) -> Option> { self.min_item().map(|(_, v)| v) } /// Returns a tuple of the max result with its key - fn max_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn max_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn max(&self) -> Option> { self.max_item().map(|(_, v)| v) } /// Returns a tuple of the median result with its key - fn median_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; + fn median_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)>; fn median(&self) -> Option> { self.median_item().map(|(_, v)| v) @@ -168,10 +156,7 @@ impl<'graph, V: NodeStateOps<'graph>> OrderedNodeStateOps<'graph> for V where V::OwnedValue: Ord, { - fn sort_by_values( - &self, - reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn sort_by_values(&self, reverse: bool) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { if reverse { self.sort_by_values_by(|a, b| a.cmp(b).reverse()) } else { @@ -179,26 +164,23 @@ where } } - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.top_k_by(Ord::cmp, k) } - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.bottom_k_by(Ord::cmp, k) } - fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn min_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.min_item_by(Ord::cmp) } - fn max_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn max_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.max_item_by(Ord::cmp) } - fn median_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn median_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.median_item_by(Ord::cmp) } } @@ -207,10 +189,7 @@ impl<'graph, V: NodeStateOps<'graph>> AsOrderedNodeStateOps<'graph> for V where for<'a> &'a V::OwnedValue: AsOrd, { - fn sort_by_values( - &self, - reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn sort_by_values(&self, reverse: bool) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { if reverse { self.sort_by_values_by(|a, b| a.as_ord().cmp(b.as_ord()).reverse()) } else { @@ -218,26 +197,23 @@ where } } - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.top_k_by(|a, b| a.as_ord().cmp(b.as_ord()), k) } - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.bottom_k_by(|a, b| a.as_ord().cmp(b.as_ord()), k) } - fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn min_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.min_item_by(|a, b| a.as_ord().cmp(b.as_ord())) } - fn max_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn max_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.max_item_by(|a, b| a.as_ord().cmp(b.as_ord())) } - fn median_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { + fn median_item(&self) -> Option<(NodeView<&Self::Graph>, Self::Value<'_>)> { self.median_item_by(|a, b| a.as_ord().cmp(b.as_ord())) } } diff --git a/raphtory/src/db/api/state/ops/filter.rs b/raphtory/src/db/api/state/ops/filter.rs new file mode 100644 index 0000000000..2d7e8af853 --- /dev/null +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -0,0 +1,227 @@ +use crate::{ + db::{ + api::{ + state::{ + ops::{Const, IntoDynNodeOp, TypeId}, + NodeOp, + }, + view::internal::GraphView, + }, + graph::{ + create_node_type_filter, + views::filter::{ + internal::CreateFilter, + model::{node_filter::NodeFilter, Filter}, + node_filtered_graph::NodeFilteredGraph, + }, + }, + }, + errors::GraphError, + prelude::{GraphViewOps, PropertyFilter}, +}; +use raphtory_api::core::{entities::VID, storage::arc_str::OptionAsStr}; +use raphtory_storage::{ + core_ops::CoreGraphOps, + graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, +}; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct Mask { + op: Op, + mask: Arc<[bool]>, +} + +impl> NodeOp for Mask { + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.mask + .get(self.op.apply(storage, node)) + .copied() + .unwrap_or(false) + } +} + +impl IntoDynNodeOp for Mask where Self: NodeOp {} + +pub trait MaskOp: Sized { + fn mask(self, mask: Arc<[bool]>) -> Mask; +} + +impl> MaskOp for Op { + fn mask(self, mask: Arc<[bool]>) -> Mask { + Mask { op: self, mask } + } +} + +pub const NO_FILTER: Const = Const(true); + +#[derive(Debug, Clone)] +pub struct NodeIdFilterOp { + filter: Filter, +} + +impl NodeIdFilterOp { + pub(crate) fn new(filter: Filter) -> Self { + Self { filter } + } +} + +impl NodeOp for NodeIdFilterOp { + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let node = storage.core_node(node); + self.filter.id_matches(node.id()) + } +} + +#[derive(Debug, Clone)] +pub struct NodeNameFilterOp { + filter: Filter, +} + +impl NodeNameFilterOp { + pub(crate) fn new(filter: Filter) -> Self { + Self { filter } + } +} + +impl NodeOp for NodeNameFilterOp { + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let node_ref = storage.core_node(node); + self.filter.matches(node_ref.name().as_str()) + } +} + +#[derive(Debug, Clone)] +pub struct NodePropertyFilterOp { + graph: G, + prop_id: usize, + filter: PropertyFilter, +} + +impl NodePropertyFilterOp { + pub(crate) fn new(graph: G, prop_id: usize, filter: PropertyFilter) -> Self { + Self { + graph, + prop_id, + filter, + } + } +} + +impl CreateFilter for PropertyFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = + NodeFilteredGraph>; + + type NodeFilter<'graph, G: GraphView + 'graph> = NodePropertyFilterOp; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let filter = self.create_node_filter(graph.clone())?; + Ok(NodeFilteredGraph::new(graph, filter)) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + let prop_id = self.resolve_prop_id(graph.node_meta(), false)?; + Ok(NodePropertyFilterOp::new(graph, prop_id, self)) + } +} + +impl NodeOp for NodePropertyFilterOp { + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let node = storage.core_node(node); + self.filter + .matches_node(&self.graph, self.prop_id, node.as_ref()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OrOp { + pub(crate) left: L, + pub(crate) right: R, +} + +impl NodeOp for OrOp +where + L: NodeOp, + R: NodeOp, +{ + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(storage, node) || self.right.apply(storage, node) + } +} + +impl IntoDynNodeOp for OrOp where Self: NodeOp + 'static {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AndOp { + pub(crate) left: L, + pub(crate) right: R, +} + +impl NodeOp for AndOp +where + L: NodeOp, + R: NodeOp, +{ + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(storage, node) && self.right.apply(storage, node) + } +} + +impl IntoDynNodeOp for AndOp where Self: NodeOp + 'static {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NotOp(pub(crate) T); + +impl IntoDynNodeOp for NotOp where Self: NodeOp + 'static {} + +impl NodeOp for NotOp +where + T: NodeOp, +{ + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + !self.0.apply(storage, node) + } +} + +pub type NodeTypeFilterOp = Mask; + +impl NodeTypeFilterOp { + pub fn new_from_values, V: AsRef>( + node_types: I, + view: impl GraphView, + ) -> Self { + let mask = create_node_type_filter(view.node_meta().node_type_meta(), node_types); + TypeId.mask(mask) + } +} + +#[cfg(test)] +mod test { + use crate::db::api::state::ops::{Const, NodeFilterOp}; + + #[test] + fn test_const() { + let c = Const(true); + assert!(!c.is_filtered()); + } +} diff --git a/raphtory/src/db/api/state/ops/history.rs b/raphtory/src/db/api/state/ops/history.rs index 3d67563ed2..61c8254d0d 100644 --- a/raphtory/src/db/api/state/ops/history.rs +++ b/raphtory/src/db/api/state/ops/history.rs @@ -1,9 +1,6 @@ -use crate::{ - db::api::{ - state::{ops::NodeOpFilter, NodeOp}, - view::internal::NodeTimeSemanticsOps, - }, - prelude::GraphViewOps, +use crate::db::api::{ + state::{ops::IntoDynNodeOp, NodeOp}, + view::internal::{GraphView, NodeTimeSemanticsOps}, }; use itertools::Itertools; use raphtory_api::core::entities::VID; @@ -11,130 +8,71 @@ use raphtory_storage::graph::graph::GraphStorage; #[derive(Debug, Clone)] pub struct EarliestTime { - pub(crate) graph: G, + pub view: G, } -impl<'graph, G: GraphViewOps<'graph>> NodeOp for EarliestTime { +impl NodeOp for EarliestTime { type Output = Option; fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = self.graph.node_time_semantics(); + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_earliest_time(node.as_ref(), &self.graph) + semantics.node_earliest_time(node.as_ref(), &self.view) } } -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for EarliestTime { - type Graph = G; - type Filtered + 'graph> = EarliestTime; - - fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - EarliestTime { - graph: filtered_graph, - } - } -} +impl IntoDynNodeOp for EarliestTime {} #[derive(Debug, Clone)] pub struct LatestTime { - pub(crate) graph: G, + pub(crate) view: G, } -impl<'graph, G: GraphViewOps<'graph>> NodeOp for LatestTime { +impl NodeOp for LatestTime { type Output = Option; fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = self.graph.node_time_semantics(); + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_latest_time(node.as_ref(), &self.graph) + semantics.node_latest_time(node.as_ref(), &self.view) } } -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for LatestTime { - type Graph = G; - type Filtered + 'graph> = LatestTime; - - fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - LatestTime { - graph: filtered_graph, - } - } -} +impl IntoDynNodeOp for LatestTime {} #[derive(Debug, Clone)] pub struct History { - pub(crate) graph: G, + pub(crate) view: G, } -impl<'graph, G: GraphViewOps<'graph>> NodeOp for History { +impl NodeOp for History { type Output = Vec; fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = self.graph.node_time_semantics(); + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); semantics - .node_history(node.as_ref(), &self.graph) + .node_history(node.as_ref(), &self.view) .dedup() .collect() } } -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for History { - type Graph = G; - type Filtered + 'graph> = History; - - fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - History { - graph: filtered_graph, - } - } -} +impl IntoDynNodeOp for History {} #[derive(Debug, Copy, Clone)] pub struct EdgeHistoryCount { - pub(crate) graph: G, + pub(crate) view: G, } -impl<'graph, G: GraphViewOps<'graph>> NodeOp for EdgeHistoryCount { +impl NodeOp for EdgeHistoryCount { type Output = usize; fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { let node = storage.core_node(node); - let ts = self.graph.node_time_semantics(); - ts.node_edge_history_count(node.as_ref(), &self.graph) + let ts = self.view.node_time_semantics(); + ts.node_edge_history_count(node.as_ref(), &self.view) } } -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for EdgeHistoryCount { - type Graph = G; - type Filtered> = EdgeHistoryCount; - - fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn filtered>(&self, graph: GH) -> Self::Filtered { - EdgeHistoryCount { graph } - } -} +impl IntoDynNodeOp for EdgeHistoryCount {} diff --git a/raphtory/src/db/api/state/ops/mod.rs b/raphtory/src/db/api/state/ops/mod.rs index 9677afc538..86bbc71d0d 100644 --- a/raphtory/src/db/api/state/ops/mod.rs +++ b/raphtory/src/db/api/state/ops/mod.rs @@ -1,7 +1,79 @@ -pub(crate) mod history; -pub(crate) mod node; +pub mod filter; +pub mod history; +pub mod node; mod properties; +use crate::db::api::view::internal::{ + filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, GraphView, +}; pub use history::*; pub use node::*; pub use properties::*; +use raphtory_api::core::{entities::VID, Direction}; +use raphtory_storage::{ + graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, + layer_ops::InternalLayerOps, +}; +use std::{ops::Deref, sync::Arc}; + +#[derive(Clone)] +pub struct NotANodeFilter; + +impl NodeOp for NotANodeFilter { + type Output = bool; + + fn apply(&self, _storage: &GraphStorage, _node: VID) -> Self::Output { + panic!("Not a node filter") + } +} + +#[derive(Debug, Clone)] +pub struct Degree { + pub(crate) dir: Direction, + pub(crate) view: G, +} + +impl NodeOp for Degree { + type Output = usize; + + fn apply(&self, storage: &GraphStorage, node: VID) -> usize { + let node = storage.core_node(node); + if matches!(self.view.filter_state(), FilterState::Neither) { + node.degree(self.view.layer_ids(), self.dir) + } else { + node.filtered_neighbours_iter(&self.view, self.view.layer_ids(), self.dir) + .count() + } + } +} + +impl IntoDynNodeOp for Degree {} + +#[derive(Debug, Copy, Clone)] +pub struct Map { + op: Op, + map: fn(Op::Output) -> V, +} + +impl NodeOp for Map { + type Output = V; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + (self.map)(self.op.apply(storage, node)) + } +} + +impl IntoDynNodeOp for Map {} + +impl<'a, V: Clone + Send + Sync> NodeOp for Arc + 'a> { + type Output = V; + fn apply(&self, storage: &GraphStorage, node: VID) -> V { + self.deref().apply(storage, node) + } +} + +impl IntoDynNodeOp for Arc> { + fn into_dynamic(self) -> Arc> { + self.clone() + } +} diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index 24e5380346..9d832e414f 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,22 +1,63 @@ -use crate::{ - db::api::view::internal::{ - time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, - }, - prelude::GraphViewOps, +use crate::db::api::state::ops::{ + filter::{AndOp, NotOp, OrOp}, + Map, }; use raphtory_api::core::{ entities::{GID, VID}, storage::arc_str::ArcStr, - Direction, }; -use raphtory_storage::{ - core_ops::CoreGraphOps, - graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, -}; -use std::{ops::Deref, sync::Arc}; +use raphtory_storage::{core_ops::CoreGraphOps, graph::graph::GraphStorage}; +use std::sync::Arc; + +pub trait NodeFilterOp: NodeOp + Clone { + fn is_filtered(&self) -> bool; + + fn and(self, other: T) -> AndOp; + + fn or(self, other: T) -> OrOp; + + fn not(self) -> NotOp; +} + +impl + Clone> NodeFilterOp for Op { + fn is_filtered(&self) -> bool { + // If there is a const true value, it is not filtered + self.const_value().is_none_or(|v| !v) + } + + fn and(self, other: T) -> AndOp { + AndOp { + left: self, + right: other, + } + } + + fn or(self, other: T) -> OrOp { + OrOp { + left: self, + right: other, + } + } + + fn not(self) -> NotOp { + NotOp { 0: self } + } +} + +pub type DynNodeFilter = Arc>; + +pub struct Eq { + left: Left, + right: Right, +} pub trait NodeOp: Send + Sync { type Output: Clone + Send + Sync; + + fn const_value(&self) -> Option { + None + } + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output; fn map(self, map: fn(Self::Output) -> V) -> Map @@ -27,18 +68,49 @@ pub trait NodeOp: Send + Sync { } } -// Cannot use OneHopFilter because there is no way to specify the bound on Output -pub trait NodeOpFilter<'graph>: NodeOp + 'graph { - type Graph: GraphViewOps<'graph>; - type Filtered>: NodeOp - + NodeOpFilter<'graph, Graph = G> - + 'graph; +pub trait IntoDynNodeOp: NodeOp + Sized + 'static { + fn into_dynamic(self) -> Arc> { + Arc::new(self) + } +} + +pub type DynNodeOp = Arc>; - fn graph(&self) -> &Self::Graph; +impl NodeOp for Eq +where + Left: NodeOp, + Right: NodeOp, + Left::Output: PartialEq, +{ + type Output = bool; - fn filtered>(&self, graph: G) -> Self::Filtered; + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(storage, node) == self.right.apply(storage, node) + } +} + +impl IntoDynNodeOp for Eq where Eq: NodeOp + 'static {} + +#[derive(Clone, Copy, Debug)] +pub struct Const(pub V); + +impl NodeOp for Const +where + V: Send + Sync + Clone, +{ + type Output = V; + + fn const_value(&self) -> Option { + Some(self.0.clone()) + } + + fn apply(&self, __storage: &GraphStorage, _node: VID) -> Self::Output { + self.0.clone() + } } +impl IntoDynNodeOp for Const {} + #[derive(Debug, Clone, Copy)] pub struct Name; @@ -50,6 +122,8 @@ impl NodeOp for Name { } } +impl IntoDynNodeOp for Name {} + #[derive(Debug, Copy, Clone)] pub struct Id; @@ -61,8 +135,11 @@ impl NodeOp for Id { } } +impl IntoDynNodeOp for Id {} + #[derive(Debug, Copy, Clone)] pub struct Type; + impl NodeOp for Type { type Output = Option; @@ -71,6 +148,8 @@ impl NodeOp for Type { } } +impl IntoDynNodeOp for Type {} + #[derive(Debug, Copy, Clone)] pub struct TypeId; impl NodeOp for TypeId { @@ -81,78 +160,4 @@ impl NodeOp for TypeId { } } -#[derive(Debug, Clone)] -pub struct Degree { - pub(crate) graph: G, - pub(crate) dir: Direction, -} - -impl<'graph, G: GraphViewOps<'graph>> NodeOp for Degree { - type Output = usize; - - fn apply(&self, storage: &GraphStorage, node: VID) -> usize { - let node = storage.core_node(node); - if matches!(self.graph.filter_state(), FilterState::Neither) { - node.degree(self.graph.layer_ids(), self.dir) - } else { - node.filtered_neighbours_iter(&self.graph, self.graph.layer_ids(), self.dir) - .count() - } - } -} - -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for Degree { - type Graph = G; - type Filtered + 'graph> = Degree; - - fn graph(&self) -> &Self::Graph { - &self.graph - } - - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - Degree { - graph: filtered_graph, - dir: self.dir, - } - } -} - -impl NodeOp for Arc> { - type Output = V; - fn apply(&self, storage: &GraphStorage, node: VID) -> V { - self.deref().apply(storage, node) - } -} - -#[derive(Debug, Copy, Clone)] -pub struct Map { - op: Op, - map: fn(Op::Output) -> V, -} - -impl NodeOp for Map { - type Output = V; - - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - (self.map)(self.op.apply(storage, node)) - } -} - -impl<'graph, Op: NodeOpFilter<'graph>, V: Clone + Send + Sync + 'graph> NodeOpFilter<'graph> - for Map -{ - type Graph = Op::Graph; - type Filtered> = Map, V>; - - fn graph(&self) -> &Self::Graph { - self.op.graph() - } - - fn filtered>(&self, graph: G) -> Self::Filtered { - let op = self.op.filtered(graph); - Map { op, map: self.map } - } -} +impl IntoDynNodeOp for TypeId {} diff --git a/raphtory/src/db/api/view/filter_ops.rs b/raphtory/src/db/api/view/filter_ops.rs index fa21ee35d9..7af5cf63ad 100644 --- a/raphtory/src/db/api/view/filter_ops.rs +++ b/raphtory/src/db/api/view/filter_ops.rs @@ -1,21 +1,21 @@ use crate::{ db::{ - api::view::internal::{BaseFilter, IterFilter}, + api::view::internal::{InternalFilter, InternalSelect}, graph::views::filter::internal::CreateFilter, }, errors::GraphError, }; -pub trait BaseFilterOps<'graph>: BaseFilter<'graph> { +pub trait Filter<'graph>: InternalFilter<'graph> { fn filter( &self, filter: F, - ) -> Result>, GraphError> { + ) -> Result>, GraphError> { Ok(self.apply_filter(filter.create_filter(self.base_graph().clone())?)) } } -pub trait IterFilterOps<'graph>: IterFilter<'graph> { +pub trait Select<'graph>: InternalSelect<'graph> { fn select( &self, filter: F, @@ -24,5 +24,5 @@ pub trait IterFilterOps<'graph>: IterFilter<'graph> { } } -impl<'graph, T: BaseFilter<'graph>> BaseFilterOps<'graph> for T {} -impl<'graph, T: IterFilter<'graph>> IterFilterOps<'graph> for T {} +impl<'graph, T: InternalFilter<'graph>> Filter<'graph> for T {} +impl<'graph, T: InternalSelect<'graph>> Select<'graph> for T {} diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index a8209e91da..bcec3c97c7 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -8,6 +8,7 @@ use crate::{ db::{ api::{ properties::{internal::InternalMetadataOps, Metadata, Properties}, + state::ops::filter::NodeTypeFilterOp, view::{internal::*, *}, }, graph::{ @@ -18,9 +19,7 @@ use crate::{ nodes::Nodes, views::{ cached_view::CachedView, - filter::{ - model::TryAsCompositeFilter, node_type_filtered_graph::NodeTypeFilteredGraph, - }, + filter::{model::TryAsCompositeFilter, node_filtered_graph::NodeFilteredGraph}, node_subgraph::NodeSubgraph, valid_graph::ValidGraph, }, @@ -61,7 +60,7 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { fn edges(&self) -> Edges<'graph, Self>; /// Return a View of the nodes in the Graph - fn nodes(&self) -> Nodes<'graph, Self, Self>; + fn nodes(&self) -> Nodes<'graph, Self>; /// Get a graph clone /// @@ -80,7 +79,7 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { fn subgraph_node_types, V: AsRef>( &self, nodes_types: I, - ) -> NodeTypeFilteredGraph; + ) -> NodeFilteredGraph; fn exclude_nodes, V: AsNodeRef>( &self, @@ -210,7 +209,7 @@ impl<'graph, G: GraphView + 'graph> GraphViewOps<'graph> for G { } } - fn nodes(&self) -> Nodes<'graph, Self, Self> { + fn nodes(&self) -> Nodes<'graph, Self> { let graph = self.clone(); Nodes::new(graph) } @@ -443,10 +442,11 @@ impl<'graph, G: GraphView + 'graph> GraphViewOps<'graph> for G { fn subgraph_node_types, V: AsRef>( &self, node_types: I, - ) -> NodeTypeFilteredGraph { - let node_types_filter = - create_node_type_filter(self.node_meta().node_type_meta(), node_types); - NodeTypeFilteredGraph::new(self.clone(), node_types_filter) + ) -> NodeFilteredGraph { + NodeFilteredGraph::new( + self.clone(), + NodeTypeFilterOp::new_from_values(node_types, self), + ) } fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { @@ -922,14 +922,14 @@ pub trait StaticGraphViewOps: GraphView + 'static {} impl StaticGraphViewOps for G {} -impl<'graph, G> BaseFilter<'graph> for G +impl<'graph, G> InternalFilter<'graph> for G where G: GraphViewOps<'graph> + 'graph, { - type BaseGraph = G; + type Graph = G; type Filtered + 'graph> = Next; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { self } diff --git a/raphtory/src/db/api/view/internal/base_filter.rs b/raphtory/src/db/api/view/internal/filter.rs similarity index 72% rename from raphtory/src/db/api/view/internal/base_filter.rs rename to raphtory/src/db/api/view/internal/filter.rs index d65bfbab8c..9b181cf108 100644 --- a/raphtory/src/db/api/view/internal/base_filter.rs +++ b/raphtory/src/db/api/view/internal/filter.rs @@ -1,14 +1,14 @@ use crate::prelude::GraphViewOps; -pub trait BaseFilter<'graph> { - type BaseGraph: GraphViewOps<'graph> + 'graph; +pub trait InternalFilter<'graph> { + type Graph: GraphViewOps<'graph> + 'graph; - type Filtered + 'graph>: BaseFilter< + type Filtered + 'graph>: InternalFilter< 'graph, - BaseGraph = FilteredGraph, + Graph = FilteredGraph, >; - fn base_graph(&self) -> &Self::BaseGraph; + fn base_graph(&self) -> &Self::Graph; fn apply_filter + 'graph>( &self, @@ -16,10 +16,10 @@ pub trait BaseFilter<'graph> { ) -> Self::Filtered; } -pub trait IterFilter<'graph> { +pub trait InternalSelect<'graph> { type IterGraph: GraphViewOps<'graph> + 'graph; - type IterFiltered + 'graph>: IterFilter<'graph>; + type IterFiltered + 'graph>: InternalSelect<'graph>; fn iter_graph(&self) -> &Self::IterGraph; diff --git a/raphtory/src/db/api/view/internal/into_dynamic.rs b/raphtory/src/db/api/view/internal/into_dynamic.rs index da7bb22d08..f54c6d4604 100644 --- a/raphtory/src/db/api/view/internal/into_dynamic.rs +++ b/raphtory/src/db/api/view/internal/into_dynamic.rs @@ -1,5 +1,5 @@ use crate::db::api::view::{ - internal::{BaseFilter, DynamicGraph, Static}, + internal::{DynamicGraph, InternalFilter, Static}, BoxableGraphView, StaticGraphViewOps, }; use std::sync::Arc; @@ -26,11 +26,11 @@ impl IntoDynamic for Arc { } } -pub trait IntoDynHop: BaseFilter<'static, BaseGraph: IntoDynamic> { +pub trait IntoDynHop: InternalFilter<'static, Graph: IntoDynamic> { fn into_dyn_hop(self) -> Self::Filtered; } -impl> IntoDynHop for T { +impl> IntoDynHop for T { fn into_dyn_hop(self) -> Self::Filtered { let graph = self.base_graph().clone().into_dynamic(); self.apply_filter(graph) diff --git a/raphtory/src/db/api/view/internal/mod.rs b/raphtory/src/db/api/view/internal/mod.rs index 2c15239959..f900944353 100644 --- a/raphtory/src/db/api/view/internal/mod.rs +++ b/raphtory/src/db/api/view/internal/mod.rs @@ -15,8 +15,8 @@ use std::{ sync::Arc, }; -mod base_filter; mod edge_filter_ops; +mod filter; mod filter_ops; mod into_dynamic; mod list_ops; @@ -25,8 +25,8 @@ mod node_filter_ops; pub(crate) mod time_semantics; mod wrapped_graph; -pub use base_filter::*; pub use edge_filter_ops::*; +pub use filter::*; pub use filter_ops::*; pub use into_dynamic::{IntoDynHop, IntoDynamic}; pub use list_ops::*; diff --git a/raphtory/src/db/api/view/layer.rs b/raphtory/src/db/api/view/layer.rs index ca5c71dcee..60cd93f821 100644 --- a/raphtory/src/db/api/view/layer.rs +++ b/raphtory/src/db/api/view/layer.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::internal::{BaseFilter, InternalLayerOps}, + api::view::internal::{InternalFilter, InternalLayerOps}, graph::views::layer_graph::LayeredGraph, }, errors::GraphError, @@ -39,8 +39,8 @@ pub trait LayerOps<'graph> { fn num_layers(&self) -> usize; } -impl<'graph, V: BaseFilter<'graph> + 'graph> LayerOps<'graph> for V { - type LayeredViewType = V::Filtered>; +impl<'graph, V: InternalFilter<'graph> + 'graph> LayerOps<'graph> for V { + type LayeredViewType = V::Filtered>; fn default_layer(&self) -> Self::LayeredViewType { let layers = match self.base_graph().get_default_layer_id() { diff --git a/raphtory/src/db/api/view/mod.rs b/raphtory/src/db/api/view/mod.rs index d3fdd7942a..6c1d81b9fc 100644 --- a/raphtory/src/db/api/view/mod.rs +++ b/raphtory/src/db/api/view/mod.rs @@ -14,7 +14,7 @@ use ouroboros::self_referencing; use std::marker::PhantomData; use crate::db::api::view::internal::{filtered_node::FilteredNodeStorageOps, GraphView}; -pub use filter_ops::{BaseFilterOps, IterFilterOps}; +pub use filter_ops::{Filter, Select}; pub use graph::*; pub use internal::{ BoxableGraphView, DynamicGraph, InheritViewOps, IntoDynHop, IntoDynamic, MaterializedGraph, diff --git a/raphtory/src/db/api/view/node.rs b/raphtory/src/db/api/view/node.rs index cc9e021e4d..f18d34315f 100644 --- a/raphtory/src/db/api/view/node.rs +++ b/raphtory/src/db/api/view/node.rs @@ -204,7 +204,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn earliest_time(&self) -> Self::ValueType> { let op = ops::EarliestTime { - graph: self.graph().clone(), + view: self.graph().clone(), }; self.map(op) } @@ -214,7 +214,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { &self, ) -> Self::ValueType, Option>>> { let op = ops::EarliestTime { - graph: self.graph().clone(), + view: self.graph().clone(), } .map(|t| t.and_then(|t| t.dt())); self.map(op) @@ -223,7 +223,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn latest_time(&self) -> Self::ValueType> { let op = ops::LatestTime { - graph: self.graph().clone(), + view: self.graph().clone(), }; self.map(op) } @@ -233,7 +233,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { &self, ) -> Self::ValueType, Option>>> { let op = ops::LatestTime { - graph: self.graph().clone(), + view: self.graph().clone(), } .map(|t| t.and_then(|t| t.dt())); self.map(op) @@ -242,7 +242,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn history(&self) -> Self::ValueType> { let op = ops::History { - graph: self.graph().clone(), + view: self.graph().clone(), }; self.map(op) } @@ -250,7 +250,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn edge_history_count(&self) -> Self::ValueType> { let op = ops::EdgeHistoryCount { - graph: self.graph().clone(), + view: self.graph().clone(), }; self.map(op) } @@ -260,7 +260,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { &self, ) -> Self::ValueType, Option>>>> { let op = ops::History { - graph: self.graph().clone(), + view: self.graph().clone(), } .map(|h| h.into_iter().map(|t| t.dt()).collect()); self.map(op) @@ -268,7 +268,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { fn is_active(&self) -> Self::ValueType, bool>> { let op = ops::History { - graph: self.graph().clone(), + view: self.graph().clone(), } .map(|h| !h.is_empty()); self.map(op) @@ -289,7 +289,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn degree(&self) -> Self::ValueType> { let op = ops::Degree { - graph: self.graph().clone(), + view: self.graph().clone(), dir: Direction::BOTH, }; self.map(op) @@ -298,8 +298,8 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn in_degree(&self) -> Self::ValueType> { let op = ops::Degree { - graph: self.graph().clone(), dir: Direction::IN, + view: self.graph().clone(), }; self.map(op) } @@ -307,7 +307,7 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { #[inline] fn out_degree(&self) -> Self::ValueType> { let op = ops::Degree { - graph: self.graph().clone(), + view: self.graph().clone(), dir: Direction::OUT, }; self.map(op) diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index 7de56237e7..5a7e215832 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -4,7 +4,7 @@ use crate::{ utils::time::{Interval, IntoTime}, }, db::api::view::{ - internal::{BaseFilter, GraphTimeSemanticsOps, InternalMaterialize}, + internal::{GraphTimeSemanticsOps, InternalFilter, InternalMaterialize}, time::internal::InternalTimeOps, }, }; @@ -18,7 +18,7 @@ use std::{ pub(crate) mod internal { use crate::{ - db::{api::view::internal::BaseFilter, graph::views::window_graph::WindowedGraph}, + db::{api::view::internal::InternalFilter, graph::views::window_graph::WindowedGraph}, prelude::{GraphViewOps, TimeOps}, }; use std::cmp::{max, min}; @@ -34,8 +34,8 @@ pub(crate) mod internal { end: Option, ) -> Self::InternalWindowedView; } - impl<'graph, E: BaseFilter<'graph> + 'graph> InternalTimeOps<'graph> for E { - type InternalWindowedView = E::Filtered>; + impl<'graph, E: InternalFilter<'graph> + 'graph> InternalTimeOps<'graph> for E { + type InternalWindowedView = E::Filtered>; fn timeline_start(&self) -> Option { self.start().or_else(|| self.base_graph().earliest_time()) @@ -157,7 +157,7 @@ pub trait TimeOps<'graph>: ParseTimeError: From<>::Error>; } -impl<'graph, V: BaseFilter<'graph> + 'graph + InternalTimeOps<'graph>> TimeOps<'graph> for V { +impl<'graph, V: InternalFilter<'graph> + 'graph + InternalTimeOps<'graph>> TimeOps<'graph> for V { type WindowedViewType = V::InternalWindowedView; fn start(&self) -> Option { diff --git a/raphtory/src/db/graph/assertions.rs b/raphtory/src/db/graph/assertions.rs index 07a22d207a..5c0c110fe3 100644 --- a/raphtory/src/db/graph/assertions.rs +++ b/raphtory/src/db/graph/assertions.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, StaticGraphViewOps}, graph::views::filter::internal::CreateFilter, }, prelude::{EdgeViewOps, Graph, GraphViewOps, NodeViewOps}, diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs index 349f879918..0558b51037 100644 --- a/raphtory/src/db/graph/edge.rs +++ b/raphtory/src/db/graph/edge.rs @@ -20,7 +20,7 @@ use crate::{ Metadata, Properties, }, view::{ - internal::{BaseFilter, EdgeTimeSemanticsOps, GraphView, Static}, + internal::{EdgeTimeSemanticsOps, GraphView, InternalFilter, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -637,14 +637,14 @@ impl From> for EdgeRef { } } -impl<'graph, Current> BaseFilter<'graph> for EdgeView +impl<'graph, Current> InternalFilter<'graph> for EdgeView where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = EdgeView; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.graph } diff --git a/raphtory/src/db/graph/edges.rs b/raphtory/src/db/graph/edges.rs index 72ee168453..b0113668ca 100644 --- a/raphtory/src/db/graph/edges.rs +++ b/raphtory/src/db/graph/edges.rs @@ -4,7 +4,7 @@ use crate::{ api::{ properties::{Metadata, Properties}, view::{ - internal::{BaseFilter, FilterOps, IterFilter, Static}, + internal::{FilterOps, InternalFilter, InternalSelect, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -44,14 +44,14 @@ impl<'graph, G: GraphViewOps<'graph>> Debug for Edges<'graph, G> { } } -impl<'graph, Current> BaseFilter<'graph> for Edges<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for Edges<'graph, Current> where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered + 'graph> = Edges<'graph, Next>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.base_graph } @@ -181,7 +181,7 @@ impl From> } } -impl<'graph, G> IterFilter<'graph> for Edges<'graph, G> +impl<'graph, G> InternalSelect<'graph> for Edges<'graph, G> where G: GraphViewOps<'graph> + 'graph, { @@ -221,19 +221,19 @@ where #[derive(Clone)] pub struct NestedEdges<'graph, G> { - pub(crate) base_graph: G, + pub(crate) graph: G, pub(crate) nodes: Arc BoxedLIter<'graph, VID> + Send + Sync + 'graph>, pub(crate) edges: Arc BoxedLIter<'graph, EdgeRef> + Send + Sync + 'graph>, } impl<'graph, G: GraphViewOps<'graph>> NestedEdges<'graph, G> { pub fn new( - base_graph: G, + graph: G, nodes: Arc BoxedLIter<'graph, VID> + Send + Sync + 'graph>, edges: Arc BoxedLIter<'graph, EdgeRef> + Send + Sync + 'graph>, ) -> Self { NestedEdges { - base_graph, + graph, nodes, edges, } @@ -248,7 +248,7 @@ impl<'graph, G: GraphViewOps<'graph>> NestedEdges<'graph, G> { } pub fn iter(&self) -> impl Iterator> + 'graph { - let base_graph = self.base_graph.clone(); + let base_graph = self.graph.clone(); let edges = self.edges.clone(); (self.nodes)().map(move |n| { let edge_fn = edges.clone(); @@ -264,15 +264,15 @@ impl<'graph, G: GraphViewOps<'graph>> NestedEdges<'graph, G> { } } -impl<'graph, Current> BaseFilter<'graph> for NestedEdges<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for NestedEdges<'graph, Current> where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered + 'graph> = NestedEdges<'graph, Next>; - fn base_graph(&self) -> &Self::BaseGraph { - &self.base_graph + fn base_graph(&self) -> &Self::Graph { + &self.graph } fn apply_filter + 'graph>( @@ -280,7 +280,7 @@ where filtered_graph: Next, ) -> Self::Filtered { NestedEdges { - base_graph: filtered_graph, + graph: filtered_graph, nodes: self.nodes.clone(), edges: self.edges.clone(), } @@ -301,7 +301,7 @@ impl<'graph, G: GraphViewOps<'graph>> BaseEdgeViewOps<'graph> for NestedEdges<'g &self, op: F, ) -> Self::ValueType { - let graph = self.base_graph.clone(); + let graph = self.graph.clone(); let edges = self.edges.clone(); (self.nodes)() .map(move |n| { @@ -324,14 +324,14 @@ impl<'graph, G: GraphViewOps<'graph>> BaseEdgeViewOps<'graph> for NestedEdges<'g &self, op: F, ) -> Self::Nodes { - let graph = self.base_graph.clone(); + let graph = self.graph.clone(); let edges = self.edges.clone(); let edges = move |n| { let graph = graph.clone(); let op = op.clone(); edges(n).map(move |e| op(&graph, e)).into_dyn_boxed() }; - PathFromGraph::new(self.base_graph.clone(), self.nodes.clone(), edges) + PathFromGraph::new(self.graph.clone(), self.nodes.clone(), edges) } fn map_exploded< @@ -341,7 +341,7 @@ impl<'graph, G: GraphViewOps<'graph>> BaseEdgeViewOps<'graph> for NestedEdges<'g &self, op: F, ) -> Self::Exploded { - let graph = self.base_graph.clone(); + let graph = self.graph.clone(); let edges = self.edges.clone(); let edges = Arc::new(move |n: VID| { let graph = graph.clone(); @@ -349,14 +349,14 @@ impl<'graph, G: GraphViewOps<'graph>> BaseEdgeViewOps<'graph> for NestedEdges<'g edges(n).flat_map(move |e| op(&graph, e)).into_dyn_boxed() }); NestedEdges { - base_graph: self.base_graph.clone(), + graph: self.graph.clone(), nodes: self.nodes.clone(), edges, } } } -impl<'graph, G> IterFilter<'graph> for NestedEdges<'graph, G> +impl<'graph, G> InternalSelect<'graph> for NestedEdges<'graph, G> where G: GraphViewOps<'graph> + 'graph, { @@ -364,7 +364,7 @@ where type IterFiltered + 'graph> = NestedEdges<'graph, G>; fn iter_graph(&self) -> &Self::IterGraph { - &self.base_graph + &self.graph } fn apply_iter_filter + 'graph>( @@ -373,7 +373,7 @@ where ) -> Self::IterFiltered { let edges = self.edges.clone(); NestedEdges { - base_graph: self.base_graph.clone(), + graph: self.graph.clone(), nodes: self.nodes.clone(), edges: Arc::new(move |vid| { let filtered_graph = filtered_graph.clone(); diff --git a/raphtory/src/db/graph/graph.rs b/raphtory/src/db/graph/graph.rs index 599476a9da..7266f6f2f4 100644 --- a/raphtory/src/db/graph/graph.rs +++ b/raphtory/src/db/graph/graph.rs @@ -19,6 +19,7 @@ use super::views::deletion_graph::PersistentGraph; use crate::{ db::{ api::{ + state::ops::NodeFilterOp, storage::storage::Storage, view::{ internal::{ @@ -232,11 +233,13 @@ pub fn assert_nodes_equal< 'graph, G1: GraphViewOps<'graph>, GH1: GraphViewOps<'graph>, + F1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, GH2: GraphViewOps<'graph>, + F2: NodeFilterOp + 'graph, >( - nodes1: &Nodes<'graph, G1, GH1>, - nodes2: &Nodes<'graph, G2, GH2>, + nodes1: &Nodes<'graph, G1, GH1, F1>, + nodes2: &Nodes<'graph, G2, GH2, F2>, ) { assert_nodes_equal_layer(nodes1, nodes2, "", false); } @@ -245,11 +248,13 @@ pub fn assert_nodes_equal_layer< 'graph, G1: GraphViewOps<'graph>, GH1: GraphViewOps<'graph>, + F1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, GH2: GraphViewOps<'graph>, + F2: NodeFilterOp + 'graph, >( - nodes1: &Nodes<'graph, G1, GH1>, - nodes2: &Nodes<'graph, G2, GH2>, + nodes1: &Nodes<'graph, G1, GH1, F1>, + nodes2: &Nodes<'graph, G2, GH2, F2>, layer_tag: &str, persistent: bool, ) { diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index 1dcb9e5ff8..d299debfca 100644 --- a/raphtory/src/db/graph/node.rs +++ b/raphtory/src/db/graph/node.rs @@ -16,7 +16,7 @@ use crate::{ }, state::NodeOp, view::{ - internal::{BaseFilter, GraphTimeSemanticsOps, NodeTimeSemanticsOps, Static}, + internal::{GraphTimeSemanticsOps, InternalFilter, NodeTimeSemanticsOps, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -172,14 +172,14 @@ impl<'graph, G: GraphViewOps<'graph>> NodeView<'graph, G> { } } -impl<'graph, Current> BaseFilter<'graph> for NodeView<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for NodeView<'graph, Current> where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = NodeView<'graph, Next>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.graph } diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 2da3645def..d100cd1684 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -2,20 +2,27 @@ use crate::{ core::entities::{edges::edge_ref::EdgeRef, nodes::node_ref::AsNodeRef, VID}, db::{ api::{ - state::{Index, LazyNodeState, NodeOp}, + state::{ + ops::{ + filter::{AndOp, NodeTypeFilterOp}, + Const, IntoDynNodeOp, NodeFilterOp, NodeOp, + }, + Index, LazyNodeState, + }, view::{ - internal::{BaseFilter, FilterOps, IterFilter, NodeList, Static}, + internal::{FilterOps, InternalFilter, NodeList, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, }, }, - graph::{create_node_type_filter, edges::NestedEdges, node::NodeView, path::PathFromGraph}, + graph::{ + edges::NestedEdges, node::NodeView, path::PathFromGraph, + views::filter::internal::CreateFilter, + }, }, + errors::GraphError, prelude::*, }; -use raphtory_storage::{ - core_ops::is_view_compatible, - graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, -}; +use raphtory_storage::{core_ops::is_view_compatible, graph::graph::GraphStorage}; use rayon::iter::ParallelIterator; use std::{ collections::HashSet, @@ -26,11 +33,11 @@ use std::{ }; #[derive(Clone)] -pub struct Nodes<'graph, G, GH = G> { +pub struct Nodes<'graph, G, GH = G, F = Const> { pub(crate) base_graph: G, - pub(crate) iter_graph: GH, + pub(crate) graph: GH, + pub(crate) selector: F, pub(crate) nodes: Option>, - pub(crate) node_types_filter: Option>, _marker: PhantomData<&'graph ()>, } @@ -38,17 +45,23 @@ impl< 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, V: AsNodeRef + Hash + Eq, S: BuildHasher, - > PartialEq> for Nodes<'graph, G, GH> + > PartialEq> for Nodes<'graph, G, GH, F> { fn eq(&self, other: &HashSet) -> bool { self.len() == other.len() && other.iter().all(|o| self.contains(o)) } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, V: AsNodeRef> PartialEq> - for Nodes<'graph, G, GH> +impl< + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + V: AsNodeRef, + > PartialEq> for Nodes<'graph, G, GH, F> { fn eq(&self, other: &Vec) -> bool { self.iter_refs() @@ -56,17 +69,27 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, V: AsNodeRef> Pa } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph> + Debug> Debug - for Nodes<'graph, G, GH> +impl< + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph + Debug, + > Debug for Nodes<'graph, G, GH, F> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> PartialEq for Nodes<'graph, G, GH> { +impl< + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, + > PartialEq for Nodes<'graph, G, GH, F> +{ fn eq(&self, other: &Self) -> bool { - if is_view_compatible(&self.base_graph, &other.base_graph) { + if is_view_compatible(&self.graph, &other.graph) { // same storage, can use internal ids self.iter_refs().eq(other.iter_refs()) } else { @@ -76,132 +99,117 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> PartialEq for No } } -impl<'graph, G: IntoDynamic, GH: IntoDynamic> Nodes<'graph, G, GH> { - pub fn into_dyn(self) -> Nodes<'graph, DynamicGraph> { - Nodes { - base_graph: self.base_graph.into_dynamic(), - iter_graph: self.iter_graph.into_dynamic(), - nodes: self.nodes, - node_types_filter: self.node_types_filter, - _marker: Default::default(), - } - } +pub trait IntoDynNodes { + fn into_dyn(self) + -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>>; } -impl<'graph, G, GH> From> for Nodes<'graph, DynamicGraph> -where - G: GraphViewOps<'graph> + IntoDynamic + Static, - GH: GraphViewOps<'graph> + IntoDynamic, +impl IntoDynNodes + for Nodes<'static, G, GH, F> { - fn from(value: Nodes<'graph, G, GH>) -> Self { + fn into_dyn( + self, + ) -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>> { Nodes { - base_graph: value.base_graph.into_dynamic(), - iter_graph: value.iter_graph.into_dynamic(), - nodes: value.nodes, - node_types_filter: value.node_types_filter, - _marker: PhantomData, + base_graph: self.base_graph.into_dynamic(), + graph: self.graph.into_dynamic(), + selector: self.selector.into_dynamic(), + nodes: self.nodes, + _marker: Default::default(), } } } -impl<'graph, G> Nodes<'graph, G, G> +impl<'graph, G> Nodes<'graph, G> where G: GraphViewOps<'graph> + Clone, { pub fn new(graph: G) -> Self { Self { base_graph: graph.clone(), - iter_graph: graph, + graph: graph.clone(), + selector: Const(true), nodes: None, - node_types_filter: None, _marker: PhantomData, } } } -impl<'graph, G, GH> Nodes<'graph, G, GH> +impl<'graph, G, GH, F> Nodes<'graph, G, GH, F> where G: GraphViewOps<'graph> + 'graph, GH: GraphViewOps<'graph> + 'graph, + F: NodeFilterOp + Clone + 'graph, { - pub fn new_filtered( - base_graph: G, - graph: GH, - nodes: Option>, - node_types_filter: Option>, - ) -> Self { + pub fn new_filtered(base_graph: G, graph: GH, selector: F, nodes: Option>) -> Self { Self { base_graph, - iter_graph: graph, + graph, + selector, nodes, - node_types_filter, _marker: PhantomData, } } pub fn node_list(&self) -> NodeList { match self.nodes.clone() { - None => self.iter_graph.node_list(), + None => self.graph.node_list(), Some(elems) => NodeList::List { elems }, } } - pub(crate) fn par_iter_refs(&self) -> impl ParallelIterator + 'graph { - let g = self.iter_graph.core_graph().lock(); - let view = self.iter_graph.clone(); - let node_types_filter = self.node_types_filter.clone(); - self.node_list().into_par_iter().filter(move |&vid| { - let node = g.core_node(vid); - node_types_filter - .as_ref() - .is_none_or(|type_filter| type_filter[node.node_type_id()]) - && view.filter_node(node.as_ref()) - }) - } - - pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH> { + pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH, F> { Nodes::new_filtered( self.base_graph.clone(), - self.iter_graph.clone(), + self.graph.clone(), + self.selector.clone(), Some(index), - self.node_types_filter.clone(), ) } + pub(crate) fn par_iter_refs(&self) -> impl ParallelIterator + 'graph { + let g = self.base_graph.core_graph().lock(); + let view = self.base_graph.clone(); + let node_select = self.selector.clone(); + self.node_list().into_par_iter().filter(move |&vid| { + let node = g.core_node(vid); + view.filter_node(node.as_ref()) && node_select.apply(&g, vid) + }) + } + #[inline] pub(crate) fn iter_refs(&self) -> impl Iterator + Send + Sync + 'graph { - let g = self.iter_graph.core_graph().lock(); - let node_types_filter = self.node_types_filter.clone(); - let view = self.iter_graph.clone(); + let g = self.base_graph.core_graph().lock(); + let view = self.base_graph.clone(); + let selector = self.selector.clone(); self.node_list().into_iter().filter(move |&vid| { let node = g.core_node(vid); - node_types_filter - .as_ref() - .is_none_or(|type_filter| type_filter[node.node_type_id()]) - && view.filter_node(node.as_ref()) + view.filter_node(node.as_ref()) && selector.apply(&g, vid) }) } - pub fn iter(&self) -> impl Iterator> + use<'_, 'graph, G, GH> { + pub fn iter(&self) -> impl Iterator> + use<'_, 'graph, G, GH, F> { self.iter_refs() - .map(|v| NodeView::new_internal(&self.base_graph, v)) + .map(|v| NodeView::new_internal(&self.graph, v)) } - pub fn iter_owned(&self) -> BoxedLIter<'graph, NodeView<'graph, G>> { - let base_graph = self.base_graph.clone(); + pub fn iter_owned(&self) -> BoxedLIter<'graph, NodeView<'graph, GH>> { + let graph = self.graph.clone(); self.iter_refs() - .map(move |v| NodeView::new_internal(base_graph.clone(), v)) + .map(move |v| NodeView::new_internal(graph.clone(), v)) .into_dyn_boxed() } - pub fn par_iter(&self) -> impl ParallelIterator> + use<'_, 'graph, G, GH> { + pub fn par_iter( + &self, + ) -> impl ParallelIterator> + use<'_, 'graph, G, GH, F> { self.par_iter_refs() - .map(|v| NodeView::new_internal(&self.base_graph, v)) + .map(|v| NodeView::new_internal(&self.graph, v)) } - pub fn into_par_iter(self) -> impl ParallelIterator> + 'graph { + pub fn into_par_iter(self) -> impl ParallelIterator> + 'graph { self.par_iter_refs() - .map(move |n| NodeView::new_internal(self.base_graph.clone(), n)) + .map(move |n| NodeView::new_internal(self.graph.clone(), n)) } /// Returns the number of nodes in the graph. @@ -212,7 +220,7 @@ where if self.is_list_filtered() { self.par_iter_refs().count() } else { - self.iter_graph.node_list().len() + self.graph.node_list().len() } } Some(nodes) => { @@ -230,25 +238,23 @@ where self.iter().next().is_none() } - pub fn get(&self, node: V) -> Option> { - let vid = self.base_graph.internalise_node(node.as_node_ref())?; + pub fn get(&self, node: V) -> Option> { + let vid = self.graph.internalise_node(node.as_node_ref())?; self.contains(vid) - .then(|| NodeView::new_internal(self.base_graph.clone(), vid)) + .then(|| NodeView::new_internal(self.graph.clone(), vid)) } pub fn type_filter, V: AsRef>( &self, node_types: I, - ) -> Nodes<'graph, G, GH> { - let node_types_filter = Some(create_node_type_filter( - self.iter_graph.node_meta().node_type_meta(), - node_types, - )); + ) -> Nodes<'graph, G, GH, AndOp> { + let node_types_filter = NodeTypeFilterOp::new_from_values(node_types, &self.graph); + let node_select = self.selector.clone().and(node_types_filter); Nodes { base_graph: self.base_graph.clone(), - iter_graph: self.iter_graph.clone(), + graph: self.graph.clone(), + selector: node_select, nodes: self.nodes.clone(), - node_types_filter, _marker: PhantomData, } } @@ -256,82 +262,93 @@ where pub fn id_filter( &self, nodes: impl IntoIterator, - ) -> Nodes<'graph, G, GH> { + ) -> Nodes<'graph, G, GH, F> { let index: Index<_> = nodes .into_iter() - .filter_map(|n| self.iter_graph.node(n).map(|n| n.node)) + .filter_map(|n| self.graph.node(n).map(|n| n.node)) .collect(); self.indexed(index) } - pub fn collect(&self) -> Vec> { + pub fn collect(&self) -> Vec> { self.iter_owned().collect() } pub fn get_metadata_id(&self, prop_name: &str) -> Option { - self.iter_graph.node_meta().get_prop_id(prop_name, true) + self.graph.node_meta().get_prop_id(prop_name, true) } pub fn get_temporal_prop_id(&self, prop_name: &str) -> Option { - self.iter_graph.node_meta().get_prop_id(prop_name, false) + self.graph.node_meta().get_prop_id(prop_name, false) } - fn is_list_filtered(&self) -> bool { - self.node_types_filter.is_some() || !self.iter_graph.node_list_trusted() + pub fn is_list_filtered(&self) -> bool { + !self.graph.node_list_trusted() || self.selector.is_filtered() } pub fn is_filtered(&self) -> bool { - self.node_types_filter.is_some() || self.iter_graph.filtered() + self.graph.filtered() || self.selector.is_filtered() } pub fn contains(&self, node: V) -> bool { - (&self.graph()) + (&self.base_graph) .node(node) .filter(|node| { - self.node_types_filter + self.nodes .as_ref() - .map(|filter| filter[node.node_type_id()]) + .map(|nodes| nodes.contains(&node.node)) .unwrap_or(true) - && self - .nodes - .as_ref() - .map(|nodes| nodes.contains(&node.node)) - .unwrap_or(true) + && self.selector.apply(self.base_graph.core_graph(), node.node) }) .is_some() } + + pub fn select( + &self, + filter: Filter, + ) -> Result>>, GraphError> { + let selector = self + .selector + .clone() + .and(filter.create_node_filter(self.base_graph.clone())?); + Ok(Nodes { + base_graph: self.base_graph.clone(), + graph: self.graph.clone(), + selector, + nodes: self.nodes.clone(), + _marker: Default::default(), + }) + } } -impl<'graph, G, GH> BaseNodeViewOps<'graph> for Nodes<'graph, G, GH> +impl<'graph, G, GH, F> BaseNodeViewOps<'graph> for Nodes<'graph, G, GH, F> where G: GraphViewOps<'graph> + 'graph, GH: GraphViewOps<'graph> + 'graph, + F: NodeFilterOp + 'graph, { - type Graph = G; - type ValueType = LazyNodeState<'graph, T, G, GH>; + type Graph = GH; + type ValueType = LazyNodeState<'graph, T, G, GH, F>; type PropType = NodeView<'graph, G>; - type PathType = PathFromGraph<'graph, G>; - type Edges = NestedEdges<'graph, G>; + type PathType = PathFromGraph<'graph, GH>; + type Edges = NestedEdges<'graph, GH>; fn graph(&self) -> &Self::Graph { - &self.base_graph + &self.graph } - fn map(&self, op: F) -> Self::ValueType - where - ::Output: 'graph, - { + fn map(&self, op: T) -> Self::ValueType { LazyNodeState::new(op, self.clone()) } fn map_edges< I: Iterator + Send + Sync + 'graph, - F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph, + T: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph, >( &self, - op: F, + op: T, ) -> Self::Edges { - let graph = self.base_graph.clone(); + let graph = self.graph.clone(); let nodes = self.clone(); let nodes = Arc::new(move || nodes.iter_refs().into_dyn_boxed()); let edges = Arc::new(move |node: VID| { @@ -339,7 +356,7 @@ where op(cg, &graph, node).into_dyn_boxed() }); NestedEdges { - base_graph: self.base_graph.clone(), + graph: self.graph.clone(), nodes, edges, } @@ -347,58 +364,32 @@ where fn hop< I: Iterator + Send + Sync + 'graph, - F: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph, + T: Fn(&GraphStorage, &Self::Graph, VID) -> I + Send + Sync + 'graph, >( &self, - op: F, + op: T, ) -> Self::PathType { - let graph = self.base_graph.clone(); + let graph = self.graph.clone(); let nodes = self.clone(); let nodes = Arc::new(move || nodes.iter_refs().into_dyn_boxed()); - PathFromGraph::new(self.base_graph.clone(), nodes, move |v| { + PathFromGraph::new(self.graph.clone(), nodes, move |v| { let cg = graph.core_graph(); op(cg, &graph, v).into_dyn_boxed() }) } } -impl<'graph, Current, G> IterFilter<'graph> for Nodes<'graph, G, Current> +impl<'graph, G, Current, F> InternalFilter<'graph> for Nodes<'graph, G, Current, F> where G: GraphViewOps<'graph> + 'graph, Current: GraphViewOps<'graph> + 'graph, + F: NodeFilterOp + Clone + 'graph, { - type IterGraph = Current; - type IterFiltered + 'graph> = - Nodes<'graph, G, FilteredGraph>; - - fn iter_graph(&self) -> &Self::IterGraph { - &self.iter_graph - } - - fn apply_iter_filter + 'graph>( - &self, - filtered_graph: FilteredGraph, - ) -> Self::IterFiltered { - Nodes { - base_graph: self.base_graph.clone(), - iter_graph: filtered_graph, - nodes: self.nodes.clone(), - node_types_filter: self.node_types_filter.clone(), - _marker: PhantomData, - } - } -} - -impl<'graph, Current, G> BaseFilter<'graph> for Nodes<'graph, Current, G> -where - Current: GraphViewOps<'graph> + 'graph, - G: GraphViewOps<'graph> + 'graph, -{ - type BaseGraph = Current; - type Filtered> = Nodes<'graph, Next, G>; + type Graph = Current; + type Filtered> = Nodes<'graph, G, Next, F>; - fn base_graph(&self) -> &Self::BaseGraph { - &self.base_graph + fn base_graph(&self) -> &Self::Graph { + &self.graph } fn apply_filter>( @@ -406,21 +397,22 @@ where filtered_graph: Next, ) -> Self::Filtered { Nodes { - base_graph: filtered_graph, - iter_graph: self.iter_graph.clone(), + base_graph: self.base_graph.clone(), + graph: filtered_graph, + selector: self.selector.clone(), nodes: self.nodes.clone(), - node_types_filter: self.node_types_filter.clone(), _marker: PhantomData, } } } -impl<'graph, G, GH> IntoIterator for Nodes<'graph, G, GH> +impl<'graph, G, GH, F> IntoIterator for Nodes<'graph, G, GH, F> where G: GraphViewOps<'graph> + 'graph, GH: GraphViewOps<'graph> + 'graph, + F: NodeFilterOp + 'graph, { - type Item = NodeView<'graph, G>; + type Item = NodeView<'graph, GH>; type IntoIter = BoxedLIter<'graph, Self::Item>; fn into_iter(self) -> Self::IntoIter { diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index c92ac38107..22e38614d9 100644 --- a/raphtory/src/db/graph/path.rs +++ b/raphtory/src/db/graph/path.rs @@ -4,7 +4,7 @@ use crate::{ api::{ state::NodeOp, view::{ - internal::{BaseFilter, FilterOps, IterFilter, Static}, + internal::{FilterOps, InternalFilter, InternalSelect, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -162,7 +162,7 @@ impl<'graph, G: GraphViewOps<'graph>> BaseNodeViewOps<'graph> for PathFromGraph< .into_dyn_boxed() }); NestedEdges { - base_graph: self.base_graph.clone(), + graph: self.base_graph.clone(), nodes: self.nodes.clone(), edges, } @@ -203,14 +203,14 @@ impl<'graph, G: GraphViewOps<'graph>> IntoIterator for PathFromGraph<'graph, G> } } -impl<'graph, Current> BaseFilter<'graph> for PathFromGraph<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for PathFromGraph<'graph, Current> where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = PathFromGraph<'graph, Next>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.base_graph } @@ -226,7 +226,7 @@ where } } -impl<'graph, G> IterFilter<'graph> for PathFromGraph<'graph, G> +impl<'graph, G> InternalSelect<'graph> for PathFromGraph<'graph, G> where G: GraphViewOps<'graph>, { @@ -423,14 +423,14 @@ impl<'graph, G: GraphViewOps<'graph>> IntoIterator for PathFromNode<'graph, G> { } } -impl<'graph, Current> BaseFilter<'graph> for PathFromNode<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for PathFromNode<'graph, Current> where Current: GraphViewOps<'graph>, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = PathFromNode<'graph, Next>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.base_graph } @@ -445,7 +445,7 @@ where } } -impl<'graph, G> IterFilter<'graph> for PathFromNode<'graph, G> +impl<'graph, G> InternalSelect<'graph> for PathFromNode<'graph, G> where G: GraphViewOps<'graph>, { diff --git a/raphtory/src/db/graph/views/filter/and_filtered_graph.rs b/raphtory/src/db/graph/views/filter/and_filtered_graph.rs index ce8397148d..31996016da 100644 --- a/raphtory/src/db/graph/views/filter/and_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/and_filtered_graph.rs @@ -2,10 +2,12 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::{filter::AndOp, NodeFilterOp}, view::internal::{ - EdgeList, Immutable, InheritMaterialize, InheritStorageOps, InheritTimeSemantics, - InternalEdgeFilterOps, InternalEdgeLayerFilterOps, InternalExplodedEdgeFilterOps, - InternalLayerOps, InternalNodeFilterOps, ListOps, NodeList, Static, + EdgeList, GraphView, Immutable, InheritMaterialize, InheritStorageOps, + InheritTimeSemantics, InternalEdgeFilterOps, InternalEdgeLayerFilterOps, + InternalExplodedEdgeFilterOps, InternalLayerOps, InternalNodeFilterOps, ListOps, + NodeList, Static, }, }, graph::views::filter::{internal::CreateFilter, model::AndFilter}, @@ -39,6 +41,11 @@ impl CreateFilter for AndFilter { where Self: 'graph; + type NodeFilter<'graph, G: GraphView + 'graph> + = AndOp, R::NodeFilter<'graph, G>> + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -53,6 +60,15 @@ impl CreateFilter for AndFilter { layer_ids, }) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + let left = self.left.create_node_filter(graph.clone())?; + let right = self.right.create_node_filter(graph.clone())?; + Ok(left.and(right)) + } } impl Base for AndFilteredGraph { diff --git a/raphtory/src/db/graph/views/filter/edge_field_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_field_filtered_graph.rs deleted file mode 100644 index e3049fa7b0..0000000000 --- a/raphtory/src/db/graph/views/filter/edge_field_filtered_graph.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{ - db::{ - api::{ - properties::internal::InheritPropertiesOps, - view::internal::{ - Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, - InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, - InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalEdgeFilterOps, Static, - }, - }, - graph::views::filter::{ - internal::CreateFilter, - model::{node_filter::NodeFilter, Filter}, - EdgeFieldFilter, - }, - }, - errors::GraphError, - prelude::GraphViewOps, -}; -use raphtory_api::{core::entities::LayerIds, inherit::Base}; -use raphtory_storage::{core_ops::InheritCoreGraphOps, graph::edges::edge_ref::EdgeStorageRef}; - -#[derive(Debug, Clone)] -pub struct EdgeFieldFilteredGraph { - graph: G, - filter: Filter, -} - -impl EdgeFieldFilteredGraph { - pub(crate) fn new(graph: G, filter: Filter) -> Self { - Self { graph, filter } - } -} - -impl CreateFilter for EdgeFieldFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = EdgeFieldFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - NodeFilter::validate(graph.id_type(), &self.0)?; - Ok(EdgeFieldFilteredGraph::new(graph, self.0)) - } -} - -impl Base for EdgeFieldFilteredGraph { - type Base = G; - - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl Static for EdgeFieldFilteredGraph {} -impl Immutable for EdgeFieldFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeFilterOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for EdgeFieldFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for EdgeFieldFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeLayerFilterOps for EdgeFieldFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritExplodedEdgeFilterOps for EdgeFieldFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InternalEdgeFilterOps for EdgeFieldFilteredGraph { - #[inline] - fn internal_edge_filtered(&self) -> bool { - true - } - - #[inline] - fn internal_edge_list_trusted(&self) -> bool { - false - } - - #[inline] - fn internal_filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { - self.graph.internal_filter_edge(edge, layer_ids) - && self.filter.matches_edge(&self.graph, edge) - } -} diff --git a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs new file mode 100644 index 0000000000..c1937c58b2 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -0,0 +1,89 @@ +use crate::{ + db::{ + api::{ + properties::internal::InheritPropertiesOps, + state::ops::NodeFilterOp, + view::internal::{ + GraphView, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, + InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, + InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, + InheritTimeSemantics, InternalEdgeFilterOps, Static, + }, + }, + graph::views::filter::model::edge_filter::Endpoint, + }, + prelude::GraphViewOps, +}; +use raphtory_api::{core::entities::LayerIds, inherit::Base}; +use raphtory_storage::{ + core_ops::InheritCoreGraphOps, + graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, +}; + +#[derive(Debug, Clone)] +pub struct EdgeNodeFilteredGraph { + graph: G, + endpoint: Endpoint, + filter: F, +} + +impl EdgeNodeFilteredGraph { + #[inline] + pub fn new(graph: G, endpoint: Endpoint, filter: F) -> Self { + Self { + graph, + endpoint, + filter, + } + } +} + +impl Base for EdgeNodeFilteredGraph { + type Base = G; + #[inline] + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for EdgeNodeFilteredGraph {} +impl Immutable for EdgeNodeFilteredGraph {} + +impl InheritCoreGraphOps for EdgeNodeFilteredGraph {} +impl InheritStorageOps for EdgeNodeFilteredGraph {} +impl InheritLayerOps for EdgeNodeFilteredGraph {} +impl InheritListOps for EdgeNodeFilteredGraph {} +impl InheritMaterialize for EdgeNodeFilteredGraph {} +impl InheritNodeFilterOps for EdgeNodeFilteredGraph {} +impl InheritPropertiesOps for EdgeNodeFilteredGraph {} +impl InheritTimeSemantics for EdgeNodeFilteredGraph {} +impl InheritNodeHistoryFilter for EdgeNodeFilteredGraph {} +impl InheritEdgeHistoryFilter for EdgeNodeFilteredGraph {} +impl InheritEdgeLayerFilterOps for EdgeNodeFilteredGraph {} +impl InheritExplodedEdgeFilterOps for EdgeNodeFilteredGraph {} + +impl InternalEdgeFilterOps for EdgeNodeFilteredGraph { + #[inline] + fn internal_edge_filtered(&self) -> bool { + true + } + + #[inline] + fn internal_edge_list_trusted(&self) -> bool { + false + } + + #[inline] + fn internal_filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { + if !self.graph.internal_filter_edge(edge, layer_ids) { + return false; + } + + let vid = match self.endpoint { + Endpoint::Src => edge.src(), + Endpoint::Dst => edge.dst(), + }; + + self.filter.apply(self.graph.core_graph(), vid) + } +} diff --git a/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs index fdf01921cf..7f542a7a66 100644 --- a/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs @@ -3,8 +3,9 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::NotANodeFilter, view::internal::{ - Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, + GraphView, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, Static, @@ -45,6 +46,8 @@ impl CreateFilter for PropertyFilter> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = EdgePropertyFilteredGraph>; + type NodeFilter<'graph, G: GraphView + 'graph> = NotANodeFilter; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -63,11 +66,20 @@ impl CreateFilter for PropertyFilter> { filter, )) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl CreateFilter for PropertyFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = EdgePropertyFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NotANodeFilter; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -75,6 +87,13 @@ impl CreateFilter for PropertyFilter { let prop_id = self.resolve_prop_id(graph.edge_meta(), graph.num_layers() > 1)?; Ok(EdgePropertyFilteredGraph::new(graph, prop_id, self)) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl Base for EdgePropertyFilteredGraph { @@ -128,21 +147,20 @@ impl<'graph, G: GraphViewOps<'graph>> InternalEdgeFilterOps for EdgePropertyFilt mod test_edge_property_filtered_graph { use crate::{ db::{ - api::view::filter_ops::BaseFilterOps, + api::view::filter_ops::Filter, graph::{ assertions::assert_ok_or_missing_edges, graph::{assert_graph_equal, assert_persistent_materialize_graph_equal}, views::{ deletion_graph::PersistentGraph, filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, - ComposableFilter, PropertyFilterFactory, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, ComposableFilter, + PropertyFilterFactory, }, }, }, }, - errors::GraphError, prelude::*, test_utils::{ build_edge_deletions, build_edge_list, build_graph_from_edge_list, build_window, @@ -154,7 +172,7 @@ mod test_edge_property_filtered_graph { use raphtory_storage::mutation::addition_ops::InternalAdditionOps; #[test] - fn test_edge_filter() { + fn test_edge_filter2() { let g = Graph::new(); g.add_edge(0, "Jimi", "John", [("band", "JH Experience")], None) .unwrap(); diff --git a/raphtory/src/db/graph/views/filter/exploded_edge_node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/exploded_edge_node_filtered_graph.rs new file mode 100644 index 0000000000..5f7e95e942 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/exploded_edge_node_filtered_graph.rs @@ -0,0 +1,115 @@ +use crate::{ + db::{ + api::{ + properties::internal::InheritPropertiesOps, + state::ops::NodeFilterOp, + view::internal::{ + GraphView, Immutable, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, + InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, + InternalExplodedEdgeFilterOps, Static, + }, + }, + graph::views::filter::model::edge_filter::Endpoint, + }, + prelude::GraphViewOps, +}; +use raphtory_api::{ + core::{ + entities::{LayerIds, ELID}, + storage::timeindex::TimeIndexEntry, + }, + inherit::Base, +}; +use raphtory_storage::{ + core_ops::InheritCoreGraphOps, + graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, +}; + +#[derive(Debug, Clone)] +pub struct ExplodedEdgeNodeFilteredGraph { + graph: G, + endpoint: Endpoint, + filter: F, +} + +impl ExplodedEdgeNodeFilteredGraph { + #[inline] + pub fn new(graph: G, endpoint: Endpoint, filter: F) -> Self { + Self { + graph, + endpoint, + filter, + } + } +} + +impl Base for ExplodedEdgeNodeFilteredGraph { + type Base = G; + #[inline] + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for ExplodedEdgeNodeFilteredGraph {} +impl Immutable for ExplodedEdgeNodeFilteredGraph {} + +impl InheritCoreGraphOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritStorageOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritLayerOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritListOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritMaterialize for ExplodedEdgeNodeFilteredGraph {} +impl InheritNodeFilterOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritPropertiesOps for ExplodedEdgeNodeFilteredGraph {} +impl InheritTimeSemantics for ExplodedEdgeNodeFilteredGraph {} +impl InheritNodeHistoryFilter + for ExplodedEdgeNodeFilteredGraph +{ +} +impl InheritEdgeHistoryFilter + for ExplodedEdgeNodeFilteredGraph +{ +} +impl InheritEdgeLayerFilterOps + for ExplodedEdgeNodeFilteredGraph +{ +} +impl InheritEdgeFilterOps for ExplodedEdgeNodeFilteredGraph {} + +impl InternalExplodedEdgeFilterOps + for ExplodedEdgeNodeFilteredGraph +{ + #[inline] + fn internal_exploded_edge_filtered(&self) -> bool { + true + } + + #[inline] + fn internal_exploded_filter_edge_list_trusted(&self) -> bool { + false + } + + #[inline] + fn internal_filter_exploded_edge( + &self, + eid: ELID, + t: TimeIndexEntry, + layer_ids: &LayerIds, + ) -> bool { + if !self.graph.internal_filter_exploded_edge(eid, t, layer_ids) { + return false; + } + + let edge = self.graph.core_edge(eid.edge); + + let vid = match self.endpoint { + Endpoint::Src => edge.src(), + Endpoint::Dst => edge.dst(), + }; + + // TODO: Fix me! Apply needs to take a graph view as input + self.filter.apply(self.graph.core_graph(), vid) + } +} diff --git a/raphtory/src/db/graph/views/filter/exploded_edge_property_filter.rs b/raphtory/src/db/graph/views/filter/exploded_edge_property_filter.rs index 6c7889fdcb..58a4d102b3 100644 --- a/raphtory/src/db/graph/views/filter/exploded_edge_property_filter.rs +++ b/raphtory/src/db/graph/views/filter/exploded_edge_property_filter.rs @@ -3,8 +3,9 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::NotANodeFilter, view::internal::{ - Immutable, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + GraphView, Immutable, InheritEdgeFilterOps, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, InternalExplodedEdgeFilterOps, Static, @@ -14,7 +15,8 @@ use crate::{ filter::{ internal::CreateFilter, model::{ - edge_filter::ExplodedEdgeFilter, property_filter::PropertyFilter, Windowed, + exploded_edge_filter::ExplodedEdgeFilter, property_filter::PropertyFilter, + Windowed, }, }, window_graph::WindowedGraph, @@ -64,6 +66,7 @@ impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilteredGraph { impl CreateFilter for PropertyFilter> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = ExplodedEdgePropertyFilteredGraph>; + type NodeFilter<'graph, G: GraphView + 'graph> = NotANodeFilter; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, @@ -83,10 +86,18 @@ impl CreateFilter for PropertyFilter> { filter, )) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl CreateFilter for PropertyFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = ExplodedEdgePropertyFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NotANodeFilter; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, @@ -95,6 +106,13 @@ impl CreateFilter for PropertyFilter { let prop_id = self.resolve_prop_id(graph.edge_meta(), graph.num_layers() > 1)?; Ok(ExplodedEdgePropertyFilteredGraph::new(graph, prop_id, self)) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl Base for ExplodedEdgePropertyFilteredGraph { @@ -172,7 +190,7 @@ impl<'graph, G: GraphViewOps<'graph>> InternalExplodedEdgeFilterOps mod test_exploded_edge_property_filtered_graph { use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, StaticGraphViewOps}, graph::{ assertions::assert_ok_or_missing_edges, edge::EdgeView, @@ -183,8 +201,9 @@ mod test_exploded_edge_property_filtered_graph { views::{ deletion_graph::PersistentGraph, filter::model::{ - edge_filter::ExplodedEdgeFilter, property_filter::PropertyFilterOps, - PropertyFilterFactory, TryAsCompositeFilter, + exploded_edge_filter::ExplodedEdgeFilter, + property_filter::PropertyFilterOps, PropertyFilterFactory, + TryAsCompositeFilter, }, }, }, diff --git a/raphtory/src/db/graph/views/filter/internal.rs b/raphtory/src/db/graph/views/filter/internal.rs index 18748ddc57..ac9a853fb9 100644 --- a/raphtory/src/db/graph/views/filter/internal.rs +++ b/raphtory/src/db/graph/views/filter/internal.rs @@ -1,4 +1,11 @@ -use crate::{errors::GraphError, prelude::GraphViewOps}; +use crate::{ + db::{ + api::{state::ops::NodeFilterOp, view::internal::GraphView}, + graph::views::filter::node_filtered_graph::NodeFilteredGraph, + }, + errors::GraphError, + prelude::GraphViewOps, +}; pub trait CreateFilter: Sized { type EntityFiltered<'graph, G>: GraphViewOps<'graph> @@ -6,8 +13,50 @@ pub trait CreateFilter: Sized { Self: 'graph, G: GraphViewOps<'graph>; + type NodeFilter<'graph, G>: NodeFilterOp + where + Self: 'graph, + G: GraphView + 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError>; + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError>; +} + +impl CreateFilter for T { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> + = NodeFilteredGraph + where + Self: 'graph; + + type NodeFilter<'graph, G: GraphView + 'graph> + = Self + where + Self: 'graph; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> + where + Self: 'graph, + { + Ok(NodeFilteredGraph::new(graph, self)) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> + where + Self: 'graph, + { + Ok(self) + } } diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index c31de6adfe..2b8d8f6cfb 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1,18 +1,11 @@ -use crate::db::graph::views::filter::model::{ - edge_filter::EdgeFieldFilter, - node_filter::{NodeNameFilter, NodeTypeFilter}, -}; - pub mod and_filtered_graph; -pub mod edge_field_filtered_graph; +pub mod edge_node_filtered_graph; pub mod edge_property_filtered_graph; +pub mod exploded_edge_node_filtered_graph; pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; -mod node_id_filtered_graph; -pub mod node_name_filtered_graph; -pub mod node_property_filtered_graph; -pub mod node_type_filtered_graph; +pub mod node_filtered_graph; pub mod not_filtered_graph; pub mod or_filtered_graph; @@ -140,14 +133,15 @@ pub(crate) mod test_filters { mod test_node_property_filter_semantics { use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, StaticGraphViewOps}, graph::{ assertions::{ assert_filter_nodes_results, assert_search_nodes_results, TestVariants, }, views::filter::{ model::{ - node_filter::NodeFilter, property_filter::PropertyFilterOps, + node_filter::NodeFilter, + property_filter::{ListAggOps, PropertyFilterOps}, PropertyFilterFactory, }, test_filters::IdentityGraphTransformer, @@ -484,7 +478,7 @@ pub(crate) mod test_filters { mod test_edge_property_filter_semantics { use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, EdgeViewOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, EdgeViewOps, StaticGraphViewOps}, graph::{ assertions::{ assert_filter_edges_results, assert_search_edges_results, @@ -1153,6 +1147,35 @@ pub(crate) mod test_filters { graph.add_node(time, id, props, node_type).unwrap(); } + let metadata = [ + ( + "1", + vec![ + ("m1", "pometry".into_prop()), + ("m2", "raphtory".into_prop()), + ], + ), + ("2", vec![("m1", "raphtory".into_prop())]), + ( + "3", + vec![ + ("m2", "pometry".into_prop()), + ("m3", "raphtory".into_prop()), + ], + ), + ( + "4", + vec![ + ("m3", "pometry".into_prop()), + ("m4", "raphtory".into_prop()), + ], + ), + ]; + + for (node_id, md) in metadata { + graph.node(node_id).unwrap().add_metadata(md).unwrap(); + } + graph } @@ -1514,20 +1537,38 @@ pub(crate) mod test_filters { #[cfg(test)] mod test_node_filter { - use crate::db::graph::{ - assertions::{assert_filter_nodes_results, assert_search_nodes_results, TestVariants}, - views::filter::{ - model::{ - node_filter::{NodeFilter, NodeFilterBuilderOps}, - ComposableFilter, + use crate::{ + db::graph::{ + assertions::{ + assert_filter_nodes_results, assert_search_nodes_results, TestVariants, }, - test_filters::{ - init_nodes_graph, init_nodes_graph_with_num_ids, init_nodes_graph_with_str_ids, - IdentityGraphTransformer, + views::filter::{ + model::{ + node_filter::{NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilderOps}, + ComposableFilter, + }, + test_filters::{ + init_nodes_graph, init_nodes_graph_with_num_ids, + init_nodes_graph_with_str_ids, IdentityGraphTransformer, + }, }, }, + prelude::{Graph, GraphViewOps, NodeViewOps, TimeOps}, }; + #[test] + fn test_node_list_is_preserved() { + let graph = init_nodes_graph(Graph::new()); + let nodes = graph + .nodes() + .after(5) + .select(NodeFilter::node_type().contains("x")) + .unwrap(); + let degrees = nodes.degree(); + let degrees_collected = degrees.compute(); + assert_eq!(degrees, degrees_collected); + } + #[test] fn test_filter_nodes_for_node_name_eq() { let filter = NodeFilter::name().eq("3"); @@ -2268,16 +2309,21 @@ pub(crate) mod test_filters { #[cfg(test)] mod test_node_property_filter { - use crate::db::graph::{ - assertions::{assert_filter_nodes_results, assert_search_nodes_results, TestVariants}, - views::filter::{ - model::{ - node_filter::NodeFilter, - not_filter::NotFilter, - property_filter::{ListAggOps, PropertyFilterOps}, - ComposableFilter, PropertyFilterFactory, + use crate::db::{ + api::state::ops::NodeFilterOp, + graph::{ + assertions::{ + assert_filter_nodes_results, assert_search_nodes_results, TestVariants, + }, + views::filter::{ + model::{ + node_filter::NodeFilter, + not_filter::NotFilter, + property_filter::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, + ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, + }, + test_filters::{init_nodes_graph, IdentityGraphTransformer}, }, - test_filters::{init_nodes_graph, IdentityGraphTransformer}, }, }; use raphtory_api::core::entities::properties::prop::Prop; @@ -7649,16 +7695,85 @@ pub(crate) mod test_filters { assertions::{assert_filter_edges_results, assert_search_edges_results, TestVariants}, views::filter::{ model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - ComposableFilter, + edge_filter::EdgeFilter, + node_filter::{NodeFilterBuilderOps, NodeIdFilterBuilderOps}, + property_filter::{ListAggOps, PropertyFilterOps}, + ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, }, test_filters::{ init_edges_graph, init_edges_graph_with_num_ids, init_edges_graph_with_str_ids, - IdentityGraphTransformer, + init_nodes_graph, IdentityGraphTransformer, }, }, }; + #[test] + fn test_filter_edges_src_property_eq() { + let filter = EdgeFilter::src().property("p10").eq("Paper_airplane"); + let expected_results = vec!["1->2", "3->1"]; + let g = |g| init_edges_graph(init_nodes_graph(g)); + assert_filter_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_filter_edges_src_property_temporal_eq() { + let filter = EdgeFilter::src() + .property("p30") + .temporal() + .first() + .eq("Old_boat"); + let expected_results = vec!["2->1", "2->3"]; + let g = |g| init_edges_graph(init_nodes_graph(g)); + assert_filter_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_filter_edges_src_metadata_eq() { + let filter = EdgeFilter::src().metadata("m1").eq("pometry"); + let expected_results = vec!["1->2"]; + let g = |g| init_edges_graph(init_nodes_graph(g)); + assert_filter_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + #[test] fn test_filter_edges_for_src_eq() { let filter = EdgeFilter::src().name().eq("3"); @@ -8153,13 +8268,13 @@ pub(crate) mod test_filters { fn test_filter_edges_for_dst_id_eq() { let filter = EdgeFilter::dst().id().eq("3"); let expected_results = vec!["2->3"]; - assert_filter_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::All, - ); + // assert_filter_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::All, + // ); assert_search_edges_results( init_edges_graph, IdentityGraphTransformer, @@ -8593,8 +8708,8 @@ pub(crate) mod test_filters { } #[test] - fn test_filter_edges_for_src_id_starts_with() { - let filter = EdgeFilter::src().id().starts_with("Tw"); + fn test_filter_edges_for_src_name_starts_with() { + let filter = EdgeFilter::src().name().starts_with("Tw"); let expected_results = vec!["Two->One", "Two->Three"]; assert_filter_edges_results( init_edges_graph_with_str_ids, @@ -8659,8 +8774,8 @@ pub(crate) mod test_filters { } #[test] - fn test_filter_edges_for_dst_id_not_contains() { - let filter = EdgeFilter::dst().id().not_contains("Par"); + fn test_filter_edges_for_dst_name_not_contains() { + let filter = EdgeFilter::dst().name().not_contains("Par"); let expected_results = vec![ "David Gilmour->John Mayer", "John Mayer->Jimmy Page", @@ -8732,17 +8847,21 @@ pub(crate) mod test_filters { #[cfg(test)] mod test_edge_property_filter { - use crate::db::graph::{ - assertions::{ - assert_filter_edges_results, assert_search_edges_results, TestGraphVariants, - TestVariants, - }, - views::filter::{ - model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, ComposableFilter, - PropertyFilterFactory, + use crate::db::{ + api::state::ops::NodeFilterOp, + graph::{ + assertions::{ + assert_filter_edges_results, assert_search_edges_results, TestGraphVariants, + TestVariants, + }, + views::filter::{ + model::{ + edge_filter::EdgeFilter, + property_filter::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, + ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, + }, + test_filters::{init_edges_graph, IdentityGraphTransformer}, }, - test_filters::{init_edges_graph, IdentityGraphTransformer}, }, }; use raphtory_api::core::entities::properties::prop::Prop; @@ -9963,24 +10082,27 @@ pub(crate) mod test_filters { #[test] fn test_edges_window_filter_on_non_temporal_property() { - let filter = EdgeFilter::window(1, 2).property("p1").eq("shivam_kapoor"); + let filter1 = EdgeFilter::window(1, 2).property("p1").eq("shivam_kapoor"); + let filter2 = EdgeFilter::window(100, 200) + .property("p1") + .eq("shivam_kapoor"); + let expected_results = vec!["1->2"]; assert_filter_edges_results( init_edges_graph, IdentityGraphTransformer, - filter.clone(), + filter1.clone(), &expected_results, TestVariants::All, ); assert_search_edges_results( init_edges_graph, IdentityGraphTransformer, - filter, + filter1.clone(), &expected_results, TestVariants::All, ); - let filter2 = EdgeFilter::window(4, 5).property("p1").eq("shivam_kapoor"); let expected_results = vec![]; assert_filter_edges_results( init_edges_graph, @@ -9992,24 +10114,26 @@ pub(crate) mod test_filters { assert_search_edges_results( init_edges_graph, IdentityGraphTransformer, - filter2, + filter2.clone(), &expected_results, TestVariants::EventOnly, ); - let filter = EdgeFilter::window(4, 5).property("p1").eq("shivam_kapoor"); + let filter2 = EdgeFilter::window(100, 200) + .property("p1") + .eq("shivam_kapoor"); let expected_results = vec!["1->2"]; assert_filter_edges_results( init_edges_graph, IdentityGraphTransformer, - filter.clone(), + filter2.clone(), &expected_results, TestVariants::PersistentOnly, ); assert_search_edges_results( init_edges_graph, IdentityGraphTransformer, - filter, + filter2.clone(), &expected_results, TestVariants::PersistentOnly, ); @@ -10105,19 +10229,18 @@ pub(crate) mod test_filters { }, views::filter::{ model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, - AndFilter, ComposableFilter, PropertyFilterFactory, TryAsCompositeFilter, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, AndFilter, ComposableFilter, + PropertyFilterFactory, TryAsCompositeFilter, }, test_filters::{init_edges_graph, IdentityGraphTransformer}, - EdgeFieldFilter, }, }; #[test] fn test_filter_edge_for_src_dst() { // TODO: PropertyFilteringNotImplemented for variants persistent_graph, persistent_disk_graph for filter_edges. - let filter: AndFilter = EdgeFilter::src() + let filter = EdgeFilter::src() .name() .eq("3") .and(EdgeFilter::dst().name().eq("1")); diff --git a/raphtory/src/db/graph/views/filter/model/and_filter.rs b/raphtory/src/db/graph/views/filter/model/and_filter.rs index 1d6b2139ad..2696e0bb61 100644 --- a/raphtory/src/db/graph/views/filter/model/and_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/and_filter.rs @@ -1,8 +1,7 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter}, - node_filter::CompositeNodeFilter, - TryAsCompositeFilter, + edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, + node_filter::CompositeNodeFilter, TryAsCompositeFilter, }, errors::GraphError, }; diff --git a/raphtory/src/db/graph/views/filter/model/edge_filter.rs b/raphtory/src/db/graph/views/filter/model/edge_filter.rs index 19a0ab8b74..530ccd0f9f 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,35 +1,45 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ + edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, model::{ - node_filter::CompositeNodeFilter, property_filter::PropertyFilter, AndFilter, - Filter, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, + exploded_edge_filter::CompositeExplodedEdgeFilter, + node_filter::{ + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, NodeIdFilterBuilder, + NodeNameFilterBuilder, NodeTypeFilterBuilder, + }, + property_filter::{ + InternalPropertyFilterBuilderOps, Op, OpChainBuilder, PropertyFilter, + PropertyRef, + }, + AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, }, }, errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::entities::GID; use raphtory_core::utils::time::IntoTime; -use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; - -#[derive(Debug, Clone)] -pub struct EdgeFieldFilter(pub Filter); +use std::{fmt, fmt::Display, sync::Arc}; -impl Display for EdgeFieldFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum Endpoint { + Src, + Dst, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeEdgeFilter { - Edge(Filter), + Src(CompositeNodeFilter), + Dst(CompositeNodeFilter), Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), + Windowed(Box>), And(Box, Box), Or(Box, Box), Not(Box), @@ -38,9 +48,10 @@ pub enum CompositeEdgeFilter { impl Display for CompositeEdgeFilter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CompositeEdgeFilter::Edge(filter) => write!(f, "{}", filter), + CompositeEdgeFilter::Src(filter) => write!(f, "SRC({})", filter), + CompositeEdgeFilter::Dst(filter) => write!(f, "DST({})", filter), CompositeEdgeFilter::Property(filter) => write!(f, "{}", filter), - CompositeEdgeFilter::PropertyWindowed(filter) => write!(f, "{}", filter), + CompositeEdgeFilter::Windowed(filter) => write!(f, "{}", filter), CompositeEdgeFilter::And(left, right) => write!(f, "({} AND {})", left, right), CompositeEdgeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), CompositeEdgeFilter::Not(filter) => write!(f, "(NOT {})", filter), @@ -50,369 +61,257 @@ impl Display for CompositeEdgeFilter { impl CreateFilter for CompositeEdgeFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type NodeFilter<'graph, G> + = NotANodeFilter + where + Self: 'graph, + G: GraphView + 'graph; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { match self { - CompositeEdgeFilter::Edge(i) => Ok(Arc::new(EdgeFieldFilter(i).create_filter(graph)?)), + CompositeEdgeFilter::Src(filter) => { + let wrapped = EndpointWrapper::new(filter, Endpoint::Src); + let filtered_graph = wrapped.create_filter(graph)?; + Ok(Arc::new(filtered_graph)) + } + CompositeEdgeFilter::Dst(filter) => { + let wrapped = EndpointWrapper::new(filter, Endpoint::Dst); + let filtered_graph = wrapped.create_filter(graph)?; + Ok(Arc::new(filtered_graph)) + } CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeEdgeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeEdgeFilter::And(l, r) => Ok(Arc::new( - AndFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), - CompositeEdgeFilter::Or(l, r) => Ok(Arc::new( - OrFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), - CompositeEdgeFilter::Not(filter) => { - let base = filter.deref().clone(); + CompositeEdgeFilter::Windowed(i) => { + let dyn_graph: Arc = Arc::new(graph); + i.create_filter(dyn_graph) + } + CompositeEdgeFilter::And(l, r) => { + let (l, r) = (*l, *r); + Ok(Arc::new( + AndFilter { left: l, right: r }.create_filter(graph)?, + )) + } + CompositeEdgeFilter::Or(l, r) => { + let (l, r) = (*l, *r); + Ok(Arc::new( + OrFilter { left: l, right: r }.create_filter(graph)?, + )) + } + CompositeEdgeFilter::Not(f) => { + let base = *f; Ok(Arc::new(NotFilter(base).create_filter(graph)?)) } } } -} - -impl TryAsCompositeFilter for CompositeEdgeFilter { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - - fn try_as_composite_edge_filter(&self) -> Result { - Ok(self.clone()) - } - - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CompositeExplodedEdgeFilter { - Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), - And( - Box, - Box, - ), - Or( - Box, - Box, - ), - Not(Box), -} - -impl Display for CompositeExplodedEdgeFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CompositeExplodedEdgeFilter::Property(filter) => write!(f, "{}", filter), - CompositeExplodedEdgeFilter::PropertyWindowed(filter) => write!(f, "{}", filter), - CompositeExplodedEdgeFilter::And(left, right) => write!(f, "({} AND {})", left, right), - CompositeExplodedEdgeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), - CompositeExplodedEdgeFilter::Not(filter) => write!(f, "(NOT {})", filter), - } - } -} - -impl CreateFilter for CompositeExplodedEdgeFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; - fn create_filter<'graph, G: GraphViewOps<'graph>>( + fn create_node_filter<'graph, G: GraphView + 'graph>( self, - graph: G, - ) -> Result, GraphError> { - match self { - CompositeExplodedEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeExplodedEdgeFilter::PropertyWindowed(i) => { - Ok(Arc::new(i.create_filter(graph)?)) - } - CompositeExplodedEdgeFilter::And(l, r) => Ok(Arc::new( - AndFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), - CompositeExplodedEdgeFilter::Or(l, r) => Ok(Arc::new( - OrFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), - CompositeExplodedEdgeFilter::Not(filter) => { - let base = filter.deref().clone(); - Ok(Arc::new(NotFilter(base).create_filter(graph)?)) - } - } + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) } } -impl TryAsCompositeFilter for CompositeExplodedEdgeFilter { +impl TryAsCompositeFilter for CompositeEdgeFilter { fn try_as_composite_node_filter(&self) -> Result { Err(GraphError::NotSupported) } fn try_as_composite_edge_filter(&self) -> Result { - Err(GraphError::NotSupported) + Ok(self.clone()) } fn try_as_composite_exploded_edge_filter( &self, ) -> Result { - Ok(self.clone()) - } -} - -pub trait InternalEdgeFilterBuilderOps: Send + Sync { - fn field_name(&self) -> &'static str; -} - -impl InternalEdgeFilterBuilderOps for Arc { - fn field_name(&self) -> &'static str { - self.deref().field_name() + Err(GraphError::NotSupported) } } -pub trait EdgeFilterOps { - fn eq(&self, value: impl Into) -> EdgeFieldFilter; - - fn ne(&self, value: impl Into) -> EdgeFieldFilter; - - fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; - - fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; - - fn starts_with(&self, value: impl Into) -> EdgeFieldFilter; - - fn ends_with(&self, value: impl Into) -> EdgeFieldFilter; - - fn contains(&self, value: impl Into) -> EdgeFieldFilter; - - fn not_contains(&self, value: impl Into) -> EdgeFieldFilter; - - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> EdgeFieldFilter; -} - -impl EdgeFilterOps for T { - fn eq(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::eq(self.field_name(), value)) - } - - fn ne(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ne(self.field_name(), value)) - } - - fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::is_in(self.field_name(), values)) - } - - fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::is_not_in(self.field_name(), values)) - } - - fn starts_with(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::starts_with(self.field_name(), value.into())) - } - - fn ends_with(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ends_with(self.field_name(), value.into())) - } +// User facing entry for building edge filters. +#[derive(Clone, Debug, Copy, Default, PartialEq, Eq)] +pub struct EdgeFilter; - fn contains(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::contains(self.field_name(), value.into())) +impl EdgeFilter { + #[inline] + pub fn src() -> EndpointWrapper { + EndpointWrapper::new(NodeFilter, Endpoint::Src) } - fn not_contains(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::not_contains(self.field_name(), value.into())) + #[inline] + pub fn dst() -> EndpointWrapper { + EndpointWrapper::new(NodeFilter, Endpoint::Dst) } - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::fuzzy_search( - self.field_name(), - value, - levenshtein_distance, - prefix_match, - )) + #[inline] + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end, EdgeFilter) } } +// Generic wrapper that pairs node-side builders with a concrete endpoint. +// The objective is to carry the endpoint through builder chain without having to change node builders +// and at the end convert into a composite node filter via TryAsCompositeFilter #[derive(Debug, Clone)] -pub struct EdgeIdFilterBuilder { - field: &'static str, +pub struct EndpointWrapper { + pub(crate) inner: T, + endpoint: Endpoint, } -impl EdgeIdFilterBuilder { +impl EndpointWrapper { #[inline] - fn field_name(&self) -> &'static str { - self.field - } - - pub fn eq>(&self, v: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::eq_id(self.field_name(), v)) - } - - pub fn ne>(&self, v: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ne_id(self.field_name(), v)) + pub fn new(inner: T, endpoint: Endpoint) -> Self { + Self { inner, endpoint } } - pub fn is_in(&self, vals: I) -> EdgeFieldFilter - where - I: IntoIterator, - V: Into, - { - EdgeFieldFilter(Filter::is_in_id(self.field_name(), vals)) + #[inline] + pub fn map(self, f: impl FnOnce(T) -> U) -> EndpointWrapper { + EndpointWrapper { + inner: f(self.inner), + endpoint: self.endpoint, + } } +} - pub fn is_not_in(&self, vals: I) -> EdgeFieldFilter - where - I: IntoIterator, - V: Into, - { - EdgeFieldFilter(Filter::is_not_in_id(self.field_name(), vals)) +impl Display for EndpointWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) } +} - pub fn lt>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::lt(self.field_name(), value)) +impl EndpointWrapper { + #[inline] + pub fn id(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::id(), self.endpoint) } - pub fn le>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::le(self.field_name(), value).into()) + #[inline] + pub fn name(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::name(), self.endpoint) } - pub fn gt>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::gt(self.field_name(), value)) + #[inline] + pub fn node_type(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::node_type(), self.endpoint) } +} - pub fn ge>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ge(self.field_name(), value)) - } +impl InternalPropertyFilterBuilderOps for EndpointWrapper { + type Filter = EndpointWrapper; + type Chained = EndpointWrapper; + type Marker = T::Marker; - pub fn starts_with>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::starts_with(self.field_name(), s.into())) + #[inline] + fn property_ref(&self) -> PropertyRef { + self.inner.property_ref() } - pub fn ends_with>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ends_with(self.field_name(), s.into())) + #[inline] + fn ops(&self) -> &[Op] { + self.inner.ops() } - pub fn contains>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::contains(self.field_name(), s.into())) + #[inline] + fn entity(&self) -> Self::Marker { + self.inner.entity() } - pub fn not_contains>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::not_contains(self.field_name(), s.into())) + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + self.wrap(self.inner.filter(filter)) } - pub fn fuzzy_search>( - &self, - s: S, - levenshtein_distance: usize, - prefix_match: bool, - ) -> EdgeFieldFilter { - EdgeFieldFilter( - Filter::fuzzy_search(self.field_name(), s, levenshtein_distance, prefix_match).into(), - ) + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + self.wrap(self.inner.chained(builder)) } } -pub struct EdgeSourceFilterBuilder; - -impl InternalEdgeFilterBuilderOps for EdgeSourceFilterBuilder { +impl InternalNodeFilterBuilderOps for EndpointWrapper { + type FilterType = T::FilterType; fn field_name(&self) -> &'static str { - "src" + self.inner.field_name() } } -pub struct EdgeDestinationFilterBuilder; - -impl InternalEdgeFilterBuilderOps for EdgeDestinationFilterBuilder { +impl InternalNodeIdFilterBuilderOps for EndpointWrapper { fn field_name(&self) -> &'static str { - "dst" + self.inner.field_name() } } -#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] -pub struct EdgeFilter; - -#[derive(Clone)] -pub enum EdgeEndpointFilter { - Src, - Dst, -} +impl TryAsCompositeFilter for EndpointWrapper { + fn try_as_composite_node_filter(&self) -> Result { + Err(GraphError::NotNodeFilter) + } -impl EdgeEndpointFilter { - pub fn id(&self) -> EdgeIdFilterBuilder { - let field = match self { - EdgeEndpointFilter::Src => "src", - EdgeEndpointFilter::Dst => "dst", + fn try_as_composite_edge_filter(&self) -> Result { + let filter = self.inner.try_as_composite_node_filter()?; + let filter = match self.endpoint { + Endpoint::Src => CompositeEdgeFilter::Src(filter), + Endpoint::Dst => CompositeEdgeFilter::Dst(filter), }; - EdgeIdFilterBuilder { field } + Ok(filter) } - pub fn name(&self) -> Arc { - match self { - EdgeEndpointFilter::Src => Arc::new(EdgeSourceFilterBuilder), - EdgeEndpointFilter::Dst => Arc::new(EdgeDestinationFilterBuilder), - } + fn try_as_composite_exploded_edge_filter( + &self, + ) -> Result { + let filter = self.inner.try_as_composite_node_filter()?; + let filter = match self.endpoint { + Endpoint::Src => CompositeExplodedEdgeFilter::Src(filter), + Endpoint::Dst => CompositeExplodedEdgeFilter::Dst(filter), + }; + Ok(filter) } } -impl EdgeFilter { - pub fn src() -> EdgeEndpointFilter { - EdgeEndpointFilter::Src - } +impl CreateFilter for EndpointWrapper { + type EntityFiltered<'graph, G> + = EdgeNodeFilteredGraph> + where + Self: 'graph, + G: GraphViewOps<'graph>; + + type NodeFilter<'graph, G> + = NotANodeFilter + where + Self: 'graph, + G: GraphView + 'graph; - pub fn dst() -> EdgeEndpointFilter { - EdgeEndpointFilter::Dst + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let filter = self.inner.create_node_filter(graph.clone())?; + Ok(EdgeNodeFilteredGraph::new(graph, self.endpoint, filter)) } - pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) } } -#[derive(Clone, Debug, Copy, Default, PartialEq, Eq)] -pub struct ExplodedEdgeFilter; +impl EntityMarker for EndpointWrapper where M: EntityMarker + Send + Sync + Clone + 'static {} -impl ExplodedEdgeFilter { - pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) - } -} +impl Wrap for EdgeFilter { + type Wrapped = T; -impl TryAsCompositeFilter for EdgeFieldFilter { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) + fn wrap(&self, value: T) -> Self::Wrapped { + value } +} - fn try_as_composite_edge_filter(&self) -> Result { - Ok(CompositeEdgeFilter::Edge(self.0.clone())) - } +impl Wrap for EndpointWrapper { + type Wrapped = EndpointWrapper; - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) + fn wrap(&self, inner: T) -> Self::Wrapped { + EndpointWrapper { + inner, + endpoint: self.endpoint, + } } } diff --git a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs new file mode 100644 index 0000000000..018d434540 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -0,0 +1,336 @@ +use crate::{ + db::{ + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, + graph::views::filter::{ + edge_node_filtered_graph::EdgeNodeFilteredGraph, + exploded_edge_node_filtered_graph::ExplodedEdgeNodeFilteredGraph, + internal::CreateFilter, + model::{ + edge_filter::{CompositeEdgeFilter, Endpoint}, + node_filter::{ + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, + }, + property_filter::{ + InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, OpChainBuilder, + PropertyFilter, PropertyFilterBuilder, PropertyRef, + }, + AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, + }, + }, + }, + errors::GraphError, + prelude::GraphViewOps, +}; +use raphtory_core::utils::time::IntoTime; +use std::{fmt, fmt::Display, sync::Arc}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompositeExplodedEdgeFilter { + Src(CompositeNodeFilter), + Dst(CompositeNodeFilter), + Property(PropertyFilter), + Windowed(Box>), + + And( + Box, + Box, + ), + Or( + Box, + Box, + ), + Not(Box), +} + +impl Display for CompositeExplodedEdgeFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CompositeExplodedEdgeFilter::Src(filter) => write!(f, "SRC({})", filter), + CompositeExplodedEdgeFilter::Dst(filter) => write!(f, "DST({})", filter), + CompositeExplodedEdgeFilter::Property(filter) => write!(f, "{}", filter), + CompositeExplodedEdgeFilter::Windowed(filter) => write!(f, "{}", filter), + CompositeExplodedEdgeFilter::And(left, right) => write!(f, "({} AND {})", left, right), + CompositeExplodedEdgeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), + CompositeExplodedEdgeFilter::Not(filter) => write!(f, "(NOT {})", filter), + } + } +} + +impl CreateFilter for CompositeExplodedEdgeFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type NodeFilter<'graph, G> + = NotANodeFilter + where + Self: 'graph, + G: GraphView + 'graph; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + match self { + Self::Src(filter) => { + let wrapped = ExplodedEndpointWrapper::new(filter, Endpoint::Src); + let filtered_graph = wrapped.create_filter(graph)?; + Ok(Arc::new(filtered_graph)) + } + Self::Dst(filter) => { + let wrapped = ExplodedEndpointWrapper::new(filter, Endpoint::Dst); + let filtered_graph = wrapped.create_filter(graph)?; + Ok(Arc::new(filtered_graph)) + } + Self::Property(p) => Ok(Arc::new(p.create_filter(graph)?)), + Self::Windowed(pw) => { + let dyn_graph: Arc = Arc::new(graph); + pw.create_filter(dyn_graph) + } + Self::And(l, r) => { + let (l, r) = (*l, *r); // move out, no clone + Ok(Arc::new( + AndFilter { left: l, right: r }.create_filter(graph)?, + )) + } + Self::Or(l, r) => { + let (l, r) = (*l, *r); + Ok(Arc::new( + OrFilter { left: l, right: r }.create_filter(graph)?, + )) + } + Self::Not(f) => { + let base = *f; + Ok(Arc::new(NotFilter(base).create_filter(graph)?)) + } + } + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } +} + +impl TryAsCompositeFilter for CompositeExplodedEdgeFilter { + fn try_as_composite_node_filter(&self) -> Result { + Err(GraphError::NotSupported) + } + + fn try_as_composite_edge_filter(&self) -> Result { + Err(GraphError::NotSupported) + } + + fn try_as_composite_exploded_edge_filter( + &self, + ) -> Result { + Ok(self.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct ExplodedEndpointWrapper { + pub(crate) inner: T, + endpoint: Endpoint, +} + +impl ExplodedEndpointWrapper { + #[inline] + pub fn new(inner: T, endpoint: Endpoint) -> Self { + Self { inner, endpoint } + } + + #[inline] + pub fn map(self, f: impl FnOnce(T) -> U) -> ExplodedEndpointWrapper { + ExplodedEndpointWrapper { + inner: f(self.inner), + endpoint: self.endpoint, + } + } +} + +impl Display for ExplodedEndpointWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl TryAsCompositeFilter for ExplodedEndpointWrapper +where + T: TryAsCompositeFilter + Clone, +{ + fn try_as_composite_node_filter(&self) -> Result { + Err(GraphError::NotSupported) + } + + fn try_as_composite_edge_filter(&self) -> Result { + Err(GraphError::NotSupported) + } + + fn try_as_composite_exploded_edge_filter( + &self, + ) -> Result { + let nf = self.inner.try_as_composite_node_filter()?; + Ok(match self.endpoint { + Endpoint::Src => CompositeExplodedEdgeFilter::Src(nf), + Endpoint::Dst => CompositeExplodedEdgeFilter::Dst(nf), + }) + } +} + +impl CreateFilter for ExplodedEndpointWrapper { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> + = ExplodedEdgeNodeFilteredGraph> + where + Self: 'graph, + G: GraphViewOps<'graph>; + + type NodeFilter<'graph, G> + = NotANodeFilter + where + Self: 'graph, + G: GraphView + 'graph; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> + where + T: 'graph, + { + let filter = self.inner.create_node_filter(graph.clone())?; + Ok(ExplodedEdgeNodeFilteredGraph::new( + graph, + self.endpoint, + filter, + )) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } +} + +impl ExplodedEndpointWrapper> +where + M: EntityMarker + Send + Sync + Clone + 'static, +{ + #[inline] + pub fn property( + &self, + name: impl Into, + ) -> PropertyFilterBuilder>> { + PropertyFilterBuilder::new( + name.into(), + ExplodedEndpointWrapper::new(self.inner.clone(), self.endpoint), + ) + } + + #[inline] + pub fn metadata( + &self, + name: impl Into, + ) -> MetadataFilterBuilder>> { + MetadataFilterBuilder::new( + name.into(), + ExplodedEndpointWrapper::new(self.inner.clone(), self.endpoint), + ) + } +} + +impl InternalPropertyFilterBuilderOps + for ExplodedEndpointWrapper +{ + type Filter = ExplodedEndpointWrapper; + type Chained = ExplodedEndpointWrapper; + + type Marker = T::Marker; + #[inline] + fn property_ref(&self) -> PropertyRef { + self.inner.property_ref() + } + + #[inline] + fn ops(&self) -> &[Op] { + self.inner.ops() + } + + #[inline] + fn entity(&self) -> Self::Marker { + self.inner.entity() + } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + self.wrap(self.inner.filter(filter)) + } + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + self.wrap(self.inner.chained(builder)) + } +} + +#[derive(Clone, Debug, Copy, Default, PartialEq, Eq)] +pub struct ExplodedEdgeFilter; + +impl ExplodedEdgeFilter { + #[inline] + pub fn src() -> ExplodedEndpointWrapper { + ExplodedEndpointWrapper::new(NodeFilter, Endpoint::Src) + } + + #[inline] + pub fn dst() -> ExplodedEndpointWrapper { + ExplodedEndpointWrapper::new(NodeFilter, Endpoint::Dst) + } + + #[inline] + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end, ExplodedEdgeFilter) + } +} + +impl InternalNodeFilterBuilderOps for ExplodedEndpointWrapper { + type FilterType = T::FilterType; + + fn field_name(&self) -> &'static str { + self.inner.field_name() + } +} + +impl InternalNodeIdFilterBuilderOps + for ExplodedEndpointWrapper +{ + fn field_name(&self) -> &'static str { + self.inner.field_name() + } +} + +impl EntityMarker for ExplodedEndpointWrapper where + M: EntityMarker + Send + Sync + Clone + 'static +{ +} + +impl Wrap for ExplodedEdgeFilter { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + +impl Wrap for ExplodedEndpointWrapper { + type Wrapped = ExplodedEndpointWrapper; + + fn wrap(&self, inner: T) -> Self::Wrapped { + ExplodedEndpointWrapper { + inner, + endpoint: self.endpoint, + } + } +} diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 909da10aea..4cc0dcaf29 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -1,29 +1,45 @@ pub(crate) use crate::db::graph::views::filter::model::and_filter::AndFilter; use crate::{ - db::graph::views::filter::model::{ - edge_filter::{ - CompositeEdgeFilter, CompositeExplodedEdgeFilter, EdgeFieldFilter, EdgeFilter, - ExplodedEdgeFilter, + db::{ + api::view::internal::GraphView, + graph::views::{ + filter::{ + internal::CreateFilter, + model::{ + edge_filter::{CompositeEdgeFilter, EdgeFilter, EndpointWrapper}, + exploded_edge_filter::{ + CompositeExplodedEdgeFilter, ExplodedEdgeFilter, ExplodedEndpointWrapper, + }, + filter_operator::FilterOperator, + node_filter::{ + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, NodeNameFilter, NodeTypeFilter, + }, + not_filter::NotFilter, + or_filter::OrFilter, + property_filter::{ + CombinedFilter, InternalPropertyFilterBuilderOps, MetadataFilterBuilder, + Op, OpChainBuilder, PropertyFilter, PropertyFilterBuilder, + PropertyFilterOps, PropertyRef, + }, + }, + }, + window_graph::WindowedGraph, }, - filter_operator::FilterOperator, - node_filter::{CompositeNodeFilter, NodeFilter, NodeNameFilter, NodeTypeFilter}, - not_filter::NotFilter, - or_filter::OrFilter, - property_filter::{MetadataFilterBuilder, PropertyFilter, PropertyFilterBuilder}, }, errors::GraphError, - prelude::{GraphViewOps, NodeViewOps}, + prelude::{GraphViewOps, TimeOps}, }; use raphtory_api::core::{ entities::{GidRef, GID}, - storage::timeindex::TimeIndexEntry, + storage::timeindex::{AsTime, TimeIndexEntry}, }; use raphtory_core::utils::time::IntoTime; -use raphtory_storage::graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}; -use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Deref, sync::Arc}; +use std::{collections::HashSet, fmt, fmt::Display, ops::Deref, sync::Arc}; pub mod and_filter; pub mod edge_filter; +pub mod exploded_edge_filter; pub mod filter_operator; pub mod node_filter; pub mod not_filter; @@ -34,24 +50,36 @@ pub mod property_filter; pub struct Windowed { pub start: TimeIndexEntry, pub end: TimeIndexEntry, - pub _marker: PhantomData, + pub inner: M, +} + +impl Display for Windowed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "WINDOW[{}..{}]({})", + self.start.t(), + self.end.t(), + self.inner + ) + } } impl Windowed { #[inline] - pub fn new(start: TimeIndexEntry, end: TimeIndexEntry) -> Self { + pub fn new(start: TimeIndexEntry, end: TimeIndexEntry, entity: M) -> Self { Self { start, end, - _marker: PhantomData, + inner: entity, } } #[inline] - pub fn from_times(start: S, end: E) -> Self { + pub fn from_times(start: S, end: E, entity: M) -> Self { let s = TimeIndexEntry::start(start.into_time()); let e = TimeIndexEntry::end(end.into_time()); - Self::new(s, e) + Self::new(s, e, entity) } } @@ -275,35 +303,101 @@ impl Filter { pub fn id_matches(&self, node_value: GidRef<'_>) -> bool { self.operator.apply_id(&self.field_value, node_value) } +} + +impl InternalNodeFilterBuilderOps for Windowed { + type FilterType = T::FilterType; + + fn field_name(&self) -> &'static str { + self.inner.field_name() + } +} + +impl InternalNodeIdFilterBuilderOps for Windowed { + fn field_name(&self) -> &'static str { + self.inner.field_name() + } +} + +impl InternalPropertyFilterBuilderOps for Windowed { + type Filter = Windowed; + type Chained = Windowed; + type Marker = T::Marker; + + fn property_ref(&self) -> PropertyRef { + self.inner.property_ref() + } + + fn ops(&self) -> &[Op] { + self.inner.ops() + } + + fn entity(&self) -> Self::Marker { + self.inner.entity() + } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + self.wrap(self.inner.filter(filter)) + } - pub fn matches_edge<'graph, G: GraphViewOps<'graph>>( + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + self.wrap(self.inner.chained(builder)) + } +} + +impl TryAsCompositeFilter for Windowed { + fn try_as_composite_node_filter(&self) -> Result { + let filter = self.inner.try_as_composite_node_filter()?; + let filter = CompositeNodeFilter::Windowed(Box::new(self.wrap(filter))); + Ok(filter) + } + + fn try_as_composite_edge_filter(&self) -> Result { + let filter = self.inner.try_as_composite_edge_filter()?; + let filter = CompositeEdgeFilter::Windowed(Box::new(self.wrap(filter))); + Ok(filter) + } + + fn try_as_composite_exploded_edge_filter( &self, - graph: &G, - edge: EdgeStorageRef, - ) -> bool { - let node_opt = match self.field_name.as_str() { - "src" => graph.node(edge.src()), - "dst" => graph.node(edge.dst()), - _ => return false, - }; + ) -> Result { + let filter = self.inner.try_as_composite_exploded_edge_filter()?; + let filter = CompositeExplodedEdgeFilter::Windowed(Box::new(self.wrap(filter))); + Ok(filter) + } +} - match &self.field_value { - FilterValue::ID(_) | FilterValue::IDSet(_) => { - if let Some(node) = node_opt { - self.id_matches(node.id().as_ref()) - } else { - // No endpoint node -> no value present. - match self.operator { - FilterOperator::Ne | FilterOperator::IsNotIn => true, - _ => false, - } - } - } - _ => { - let name_opt = node_opt.map(|n| n.name()); - self.matches(name_opt.as_deref()) - } - } +impl CreateFilter for Windowed { + type EntityFiltered<'graph, G> + = T::EntityFiltered<'graph, WindowedGraph> + where + G: GraphViewOps<'graph>; + + type NodeFilter<'graph, G> + = T::NodeFilter<'graph, WindowedGraph> + where + G: GraphView + 'graph; + + fn create_filter<'graph, G>( + self, + graph: G, + ) -> Result, GraphError> + where + G: GraphViewOps<'graph>, + { + self.inner + .create_filter(graph.window(self.start.t(), self.end.t())) + } + + fn create_node_filter<'graph, G>( + self, + graph: G, + ) -> Result, GraphError> + where + G: GraphView + 'graph, + { + self.inner + .create_node_filter(graph.window(self.start.t(), self.end.t())) } } @@ -357,10 +451,11 @@ pub trait ComposableFilter: Sized { impl ComposableFilter for PropertyFilter {} impl ComposableFilter for NodeNameFilter {} impl ComposableFilter for NodeTypeFilter {} -impl ComposableFilter for EdgeFieldFilter {} +impl ComposableFilter for EndpointWrapper where T: TryAsCompositeFilter + Clone {} impl ComposableFilter for AndFilter {} impl ComposableFilter for OrFilter {} impl ComposableFilter for NotFilter {} +impl ComposableFilter for Windowed {} trait EntityMarker: Clone + Send + Sync {} @@ -372,17 +467,228 @@ impl EntityMarker for ExplodedEdgeFilter {} impl EntityMarker for Windowed {} -pub trait PropertyFilterFactory: Sized { - fn property(&self, name: impl Into) -> PropertyFilterBuilder; +impl Wrap for Windowed { + type Wrapped = Windowed; + + fn wrap(&self, value: T) -> Self::Wrapped { + Windowed::new(self.start, self.end, value) + } +} + +pub trait Wrap { + type Wrapped; + + fn wrap(&self, value: T) -> Self::Wrapped; +} + +impl Wrap for Arc { + type Wrapped = S::Wrapped; + fn wrap(&self, value: T) -> Self::Wrapped { + self.deref().wrap(value) + } +} + +pub trait InternalPropertyFilterFactory { + type Entity: Clone + Send + Sync + 'static; + type PropertyBuilder: InternalPropertyFilterBuilderOps + TemporalPropertyFilterFactory; + type MetadataBuilder: InternalPropertyFilterBuilderOps; + + fn entity(&self) -> Self::Entity; + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder; + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder; +} + +pub trait PropertyFilterFactory: InternalPropertyFilterFactory { + fn property(&self, name: impl Into) -> Self::PropertyBuilder { + let builder = PropertyFilterBuilder::new(name, self.entity()); + self.property_builder(builder) + } + + fn metadata(&self, name: impl Into) -> Self::MetadataBuilder { + let builder = MetadataFilterBuilder::new(name, self.entity()); + self.metadata_builder(builder) + } +} + +impl PropertyFilterFactory for T {} + +pub trait TemporalPropertyFilterFactory: InternalPropertyFilterBuilderOps { + fn temporal(&self) -> Self::Chained { + let builder = OpChainBuilder { + prop_ref: PropertyRef::TemporalProperty(self.property_ref().name().to_string()), + ops: vec![], + entity: self.entity(), + }; + self.chained(builder) + } +} + +impl InternalPropertyFilterFactory for NodeFilter { + type Entity = NodeFilter; + type PropertyBuilder = PropertyFilterBuilder; + type MetadataBuilder = MetadataFilterBuilder; + + fn entity(&self) -> Self::Entity { + NodeFilter + } + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + builder + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + builder + } +} + +impl InternalPropertyFilterFactory for EdgeFilter { + type Entity = EdgeFilter; + type PropertyBuilder = PropertyFilterBuilder; + type MetadataBuilder = MetadataFilterBuilder; + + fn entity(&self) -> Self::Entity { + EdgeFilter + } + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + builder + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + builder + } +} + +impl InternalPropertyFilterFactory for ExplodedEdgeFilter { + type Entity = ExplodedEdgeFilter; + type PropertyBuilder = PropertyFilterBuilder; + type MetadataBuilder = MetadataFilterBuilder; + + fn entity(&self) -> Self::Entity { + ExplodedEdgeFilter + } + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + builder + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + builder + } +} + +impl InternalPropertyFilterFactory for Windowed { + type Entity = T::Entity; + type PropertyBuilder = Windowed; + type MetadataBuilder = Windowed; + + fn entity(&self) -> Self::Entity { + self.inner.entity() + } + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + self.wrap(self.inner.property_builder(builder)) + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + self.wrap(self.inner.metadata_builder(builder)) + } +} + +impl TemporalPropertyFilterFactory for Windowed {} + +impl InternalPropertyFilterFactory for EndpointWrapper { + type Entity = T::Entity; + type PropertyBuilder = EndpointWrapper; + type MetadataBuilder = EndpointWrapper; + + fn entity(&self) -> Self::Entity { + self.inner.entity() + } - fn metadata(&self, name: impl Into) -> MetadataFilterBuilder; + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + self.wrap(self.inner.property_builder(builder)) + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + self.wrap(self.inner.metadata_builder(builder)) + } } -impl PropertyFilterFactory for M { - fn property(&self, name: impl Into) -> PropertyFilterBuilder { - PropertyFilterBuilder::new(name, self.clone()) +impl TemporalPropertyFilterFactory for EndpointWrapper {} + +impl InternalPropertyFilterFactory + for ExplodedEndpointWrapper +{ + type Entity = T::Entity; + type PropertyBuilder = ExplodedEndpointWrapper; + type MetadataBuilder = ExplodedEndpointWrapper; + + fn entity(&self) -> Self::Entity { + self.inner.entity() } - fn metadata(&self, name: impl Into) -> MetadataFilterBuilder { - MetadataFilterBuilder::new(name, self.clone()) + + fn property_builder( + &self, + builder: PropertyFilterBuilder, + ) -> Self::PropertyBuilder { + self.wrap(self.inner.property_builder(builder)) + } + + fn metadata_builder( + &self, + builder: MetadataFilterBuilder, + ) -> Self::MetadataBuilder { + self.wrap(self.inner.metadata_builder(builder)) } } + +impl TemporalPropertyFilterFactory + for ExplodedEndpointWrapper +{ +} + +impl TemporalPropertyFilterFactory for PropertyFilterBuilder +where + T: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, +{ +} diff --git a/raphtory/src/db/graph/views/filter/model/node_filter.rs b/raphtory/src/db/graph/views/filter/model/node_filter.rs index 5f6284ada4..d0d480e167 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,15 +1,27 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::{ + ops::{ + filter::{ + AndOp, MaskOp, NodeIdFilterOp, NodeNameFilterOp, NodeTypeFilterOp, NotOp, + OrOp, + }, + TypeId, + }, + NodeOp, + }, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter}, - filter_operator::FilterOperator, - property_filter::PropertyFilter, - AndFilter, Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, - Windowed, + edge_filter::CompositeEdgeFilter, + exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, + property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, + OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, + node_filtered_graph::NodeFilteredGraph, }, }, errors::GraphError, @@ -34,6 +46,28 @@ impl From for NodeIdFilter { } } +impl CreateFilter for NodeIdFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; + + type NodeFilter<'graph, G: GraphView + 'graph> = NodeIdFilterOp; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + NodeFilter::validate(graph.id_type(), &self.0)?; + Ok(NodeFilteredGraph::new(graph, NodeIdFilterOp::new(self.0))) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + NodeFilter::validate(graph.id_type(), &self.0)?; + Ok(NodeIdFilterOp::new(self.0)) + } +} + #[derive(Debug, Clone)] pub struct NodeNameFilter(pub Filter); @@ -49,6 +83,26 @@ impl From for NodeNameFilter { } } +impl CreateFilter for NodeNameFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; + + type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilterOp; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + Ok(NodeFilteredGraph::new(graph, NodeNameFilterOp::new(self.0))) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Ok(NodeNameFilterOp::new(self.0)) + } +} + #[derive(Debug, Clone)] pub struct NodeTypeFilter(pub Filter); @@ -64,11 +118,48 @@ impl From for NodeTypeFilter { } } +impl CreateFilter for NodeTypeFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; + + type NodeFilter<'graph, G: GraphView + 'graph> = NodeTypeFilterOp; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let node_types_filter = graph + .node_meta() + .node_type_meta() + .get_keys() + .iter() + .map(|k| self.0.matches(Some(k))) // TODO: _default check + .collect::>(); + Ok(NodeFilteredGraph::new( + graph, + TypeId.mask(node_types_filter.into()), + )) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + let node_types_filter = graph + .node_meta() + .node_type_meta() + .get_keys() + .iter() + .map(|k| self.0.matches(Some(k))) // TODO: _default check + .collect::>(); + Ok(TypeId.mask(node_types_filter.into())) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeNodeFilter { Node(Filter), Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), + Windowed(Box>), And(Box, Box), Or(Box, Box), Not(Box), @@ -78,7 +169,7 @@ impl Display for CompositeNodeFilter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CompositeNodeFilter::Property(filter) => write!(f, "{}", filter), - CompositeNodeFilter::PropertyWindowed(filter) => write!(f, "{}", filter), + CompositeNodeFilter::Windowed(filter) => write!(f, "{}", filter), CompositeNodeFilter::Node(filter) => write!(f, "{}", filter), CompositeNodeFilter::And(left, right) => write!(f, "({} AND {})", left, right), CompositeNodeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), @@ -88,40 +179,47 @@ impl Display for CompositeNodeFilter { } impl CreateFilter for CompositeNodeFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = + NodeFilteredGraph>; + + type NodeFilter<'graph, G: GraphView + 'graph> = Arc + 'graph>; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { + let filter = self.create_node_filter(graph.clone())?; + Ok(NodeFilteredGraph::new(graph, filter)) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { match self { CompositeNodeFilter::Node(i) => match i.field_name.as_str() { - "node_id" => Ok(Arc::new(NodeIdFilter(i).create_filter(graph)?)), - "node_name" => Ok(Arc::new(NodeNameFilter(i).create_filter(graph)?)), - "node_type" => Ok(Arc::new(NodeTypeFilter(i).create_filter(graph)?)), + "node_id" => Ok(Arc::new(NodeIdFilter(i).create_node_filter(graph)?)), + "node_name" => Ok(Arc::new(NodeNameFilter(i).create_node_filter(graph)?)), + "node_type" => Ok(Arc::new(NodeTypeFilter(i).create_node_filter(graph)?)), _ => { unreachable!() } }, - CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeNodeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeNodeFilter::And(l, r) => Ok(Arc::new( - AndFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), - CompositeNodeFilter::Or(l, r) => Ok(Arc::new( - OrFilter { - left: l.deref().clone(), - right: r.deref().clone(), - } - .create_filter(graph)?, - )), + CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_node_filter(graph)?)), + CompositeNodeFilter::Windowed(i) => { + let dyn_graph: Arc = Arc::new(graph); + i.create_node_filter(dyn_graph) + } + CompositeNodeFilter::And(l, r) => Ok(Arc::new(AndOp { + left: l.clone().create_node_filter(graph.clone())?, + right: r.clone().create_node_filter(graph.clone())?, + })), + CompositeNodeFilter::Or(l, r) => Ok(Arc::new(OrOp { + left: l.clone().create_node_filter(graph.clone())?, + right: r.clone().create_node_filter(graph.clone())?, + })), CompositeNodeFilter::Not(filter) => { - let base = filter.deref().clone(); - Ok(Arc::new(NotFilter(base).create_filter(graph)?)) + Ok(Arc::new(NotOp(filter.clone().create_node_filter(graph)?))) } } } @@ -143,51 +241,60 @@ impl TryAsCompositeFilter for CompositeNodeFilter { } } -pub trait InternalNodeFilterBuilderOps: Send + Sync { - type NodeFilterType: From + CreateFilter + TryAsCompositeFilter + Clone + 'static; - +pub trait InternalNodeFilterBuilderOps: Send + Sync + Wrap { + type FilterType: From; fn field_name(&self) -> &'static str; } impl InternalNodeFilterBuilderOps for Arc { - type NodeFilterType = T::NodeFilterType; - + type FilterType = T::FilterType; fn field_name(&self) -> &'static str { self.deref().field_name() } } pub trait NodeFilterBuilderOps: InternalNodeFilterBuilderOps { - fn eq(&self, value: impl Into) -> Self::NodeFilterType { - Filter::eq(self.field_name(), value).into() + fn eq(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::eq(self.field_name(), value); + self.wrap(filter.into()) } - fn ne(&self, value: impl Into) -> Self::NodeFilterType { - Filter::ne(self.field_name(), value).into() + fn ne(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::ne(self.field_name(), value); + self.wrap(filter.into()) } - fn is_in(&self, values: impl IntoIterator) -> Self::NodeFilterType { - Filter::is_in(self.field_name(), values).into() + fn is_in(&self, values: impl IntoIterator) -> Self::Wrapped { + let filter = Filter::is_in(self.field_name(), values); + self.wrap(filter.into()) } - fn is_not_in(&self, values: impl IntoIterator) -> Self::NodeFilterType { - Filter::is_not_in(self.field_name(), values).into() + fn is_not_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped { + let filter = Filter::is_not_in(self.field_name(), values); + self.wrap(filter.into()) } - fn starts_with(&self, value: impl Into) -> Self::NodeFilterType { - Filter::starts_with(self.field_name(), value).into() + fn starts_with(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::starts_with(self.field_name(), value); + self.wrap(filter.into()) } - fn ends_with(&self, value: impl Into) -> Self::NodeFilterType { - Filter::ends_with(self.field_name(), value).into() + fn ends_with(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::ends_with(self.field_name(), value); + self.wrap(filter.into()) } - fn contains(&self, value: impl Into) -> Self::NodeFilterType { - Filter::contains(self.field_name(), value).into() + fn contains(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::contains(self.field_name(), value); + self.wrap(filter.into()) } - fn not_contains(&self, value: impl Into) -> Self::NodeFilterType { - Filter::not_contains(self.field_name(), value.into()).into() + fn not_contains(&self, value: impl Into) -> Self::Wrapped { + let filter = Filter::not_contains(self.field_name(), value.into()); + self.wrap(filter.into()) } fn fuzzy_search( @@ -195,107 +302,163 @@ pub trait NodeFilterBuilderOps: InternalNodeFilterBuilderOps { value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> Self::NodeFilterType { - Filter::fuzzy_search(self.field_name(), value, levenshtein_distance, prefix_match).into() + ) -> Self::Wrapped { + let filter = + Filter::fuzzy_search(self.field_name(), value, levenshtein_distance, prefix_match); + self.wrap(filter.into()) } } impl NodeFilterBuilderOps for T {} -pub struct NodeIdFilterBuilder; +pub trait InternalNodeIdFilterBuilderOps: Send + Sync + Wrap { + fn field_name(&self) -> &'static str; +} -impl NodeIdFilterBuilder { - #[inline] +impl InternalNodeIdFilterBuilderOps for Arc { fn field_name(&self) -> &'static str { - "node_id" + self.deref().field_name() } +} - pub fn eq>(&self, value: T) -> NodeIdFilter { - Filter::eq_id(self.field_name(), value).into() +pub trait NodeIdFilterBuilderOps: InternalNodeIdFilterBuilderOps { + fn eq>(&self, value: T) -> Self::Wrapped { + let filter = Filter::eq_id(self.field_name(), value); + self.wrap(NodeIdFilter(filter)) } - pub fn ne>(&self, value: T) -> NodeIdFilter { - Filter::ne_id(self.field_name(), value).into() + fn ne>(&self, value: T) -> Self::Wrapped { + let filter = Filter::ne_id(self.field_name(), value).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn is_in(&self, values: I) -> NodeIdFilter + fn is_in(&self, values: I) -> Self::Wrapped where I: IntoIterator, T: Into, { - Filter::is_in_id(self.field_name(), values).into() + let filter = Filter::is_in_id(self.field_name(), values).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn is_not_in(&self, values: I) -> NodeIdFilter + fn is_not_in(&self, values: I) -> Self::Wrapped where I: IntoIterator, T: Into, { - Filter::is_not_in_id(self.field_name(), values).into() + let filter = Filter::is_not_in_id(self.field_name(), values).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn lt>(&self, value: V) -> NodeIdFilter { - Filter::lt(self.field_name(), value).into() + fn lt>(&self, value: V) -> Self::Wrapped { + let filter = Filter::lt(self.field_name(), value).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn le>(&self, value: V) -> NodeIdFilter { - Filter::le(self.field_name(), value).into() + fn le>(&self, value: V) -> Self::Wrapped { + let filter = Filter::le(self.field_name(), value).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn gt>(&self, value: V) -> NodeIdFilter { - Filter::gt(self.field_name(), value).into() + fn gt>(&self, value: V) -> Self::Wrapped { + let filter = Filter::gt(self.field_name(), value).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn ge>(&self, value: V) -> NodeIdFilter { - Filter::ge(self.field_name(), value).into() + fn ge>(&self, value: V) -> Self::Wrapped { + let filter = Filter::ge(self.field_name(), value).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn starts_with>(&self, s: S) -> NodeIdFilter { - Filter::starts_with(self.field_name(), s.into()).into() + fn starts_with>(&self, s: S) -> Self::Wrapped { + let filter = Filter::starts_with(self.field_name(), s.into()).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn ends_with>(&self, s: S) -> NodeIdFilter { - Filter::ends_with(self.field_name(), s.into()).into() + fn ends_with>(&self, s: S) -> Self::Wrapped { + let filter = Filter::ends_with(self.field_name(), s.into()).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn contains>(&self, s: S) -> NodeIdFilter { - Filter::contains(self.field_name(), s.into()).into() + fn contains>(&self, s: S) -> Self::Wrapped { + let filter = Filter::contains(self.field_name(), s.into()).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn not_contains>(&self, s: S) -> NodeIdFilter { - Filter::not_contains(self.field_name(), s.into()).into() + fn not_contains>(&self, s: S) -> Self::Wrapped { + let filter = Filter::not_contains(self.field_name(), s.into()).into(); + self.wrap(NodeIdFilter(filter)) } - pub fn fuzzy_search>( + fn fuzzy_search>( &self, s: S, levenshtein_distance: usize, prefix_match: bool, - ) -> NodeIdFilter { - Filter::fuzzy_search(self.field_name(), s, levenshtein_distance, prefix_match).into() + ) -> Self::Wrapped { + let filter = + Filter::fuzzy_search(self.field_name(), s, levenshtein_distance, prefix_match).into(); + self.wrap(NodeIdFilter(filter)) } } +impl NodeIdFilterBuilderOps for T {} + +#[derive(Clone, Debug)] +pub struct NodeIdFilterBuilder; + +impl Wrap for NodeIdFilterBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + +impl InternalNodeIdFilterBuilderOps for NodeIdFilterBuilder { + #[inline] + fn field_name(&self) -> &'static str { + "node_id" + } +} + +#[derive(Clone, Debug)] pub struct NodeNameFilterBuilder; impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { - type NodeFilterType = NodeNameFilter; + type FilterType = NodeNameFilter; fn field_name(&self) -> &'static str { "node_name" } } +impl Wrap for NodeNameFilterBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + +#[derive(Clone, Debug)] pub struct NodeTypeFilterBuilder; impl InternalNodeFilterBuilderOps for NodeTypeFilterBuilder { - type NodeFilterType = NodeTypeFilter; - + type FilterType = NodeTypeFilter; fn field_name(&self) -> &'static str { "node_type" } } +impl Wrap for NodeTypeFilterBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + #[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] pub struct NodeFilter; @@ -313,7 +476,7 @@ impl NodeFilter { } pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + Windowed::from_times(start, end, NodeFilter) } pub fn validate(id_dtype: Option, filter: &Filter) -> Result<(), GraphError> { @@ -478,3 +641,11 @@ impl TryAsCompositeFilter for NodeTypeFilter { Err(GraphError::NotSupported) } } + +impl Wrap for NodeFilter { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} diff --git a/raphtory/src/db/graph/views/filter/model/not_filter.rs b/raphtory/src/db/graph/views/filter/model/not_filter.rs index 26981a3449..5fb66a434c 100644 --- a/raphtory/src/db/graph/views/filter/model/not_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/not_filter.rs @@ -1,8 +1,7 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter}, - node_filter::CompositeNodeFilter, - TryAsCompositeFilter, + edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, + node_filter::CompositeNodeFilter, TryAsCompositeFilter, }, errors::GraphError, }; diff --git a/raphtory/src/db/graph/views/filter/model/or_filter.rs b/raphtory/src/db/graph/views/filter/model/or_filter.rs index 5f13a2cad8..7cb08da838 100644 --- a/raphtory/src/db/graph/views/filter/model/or_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/or_filter.rs @@ -1,8 +1,7 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter}, - node_filter::CompositeNodeFilter, - TryAsCompositeFilter, + edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, + node_filter::CompositeNodeFilter, TryAsCompositeFilter, }, errors::GraphError, }; diff --git a/raphtory/src/db/graph/views/filter/model/property_filter.rs b/raphtory/src/db/graph/views/filter/model/property_filter.rs index 8e2b413396..dd0975831c 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -11,14 +11,15 @@ use crate::{ graph::{ edge::EdgeView, node::NodeView, - views::filter::model::{ - edge_filter::{ - CompositeEdgeFilter, CompositeExplodedEdgeFilter, EdgeFilter, - ExplodedEdgeFilter, + views::filter::{ + internal::CreateFilter, + model::{ + edge_filter::{CompositeEdgeFilter, EdgeFilter}, + exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, + filter_operator::FilterOperator, + node_filter::{CompositeNodeFilter, NodeFilter}, + TryAsCompositeFilter, Wrap, }, - filter_operator::FilterOperator, - node_filter::{CompositeNodeFilter, NodeFilter}, - TryAsCompositeFilter, Windowed, }, }, }, @@ -1345,19 +1346,31 @@ impl TryAsCompositeFilter for PropertyFilter { } } -pub trait InternalPropertyFilterOps: Send + Sync { - type Marker: Clone + Send + Sync + 'static; +pub trait CombinedFilter: CreateFilter + TryAsCompositeFilter + Clone + 'static {} + +impl CombinedFilter for T {} + +pub trait InternalPropertyFilterBuilderOps: Send + Sync { + type Filter: CombinedFilter; + + type Chained: InternalPropertyFilterBuilderOps; + + type Marker: Send + Sync + Clone + 'static; fn property_ref(&self) -> PropertyRef; - fn ops(&self) -> &[Op] { - &[] - } + fn ops(&self) -> &[Op]; fn entity(&self) -> Self::Marker; + + fn filter(&self, filter: PropertyFilter) -> Self::Filter; + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained; } -impl InternalPropertyFilterOps for Arc { +impl InternalPropertyFilterBuilderOps for Arc { + type Filter = T::Filter; + type Chained = T::Chained; type Marker = T::Marker; fn property_ref(&self) -> PropertyRef { @@ -1371,170 +1384,192 @@ impl InternalPropertyFilterOps for Arc { fn entity(&self) -> Self::Marker { self.deref().entity() } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + self.deref().filter(filter) + } + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + self.deref().chained(builder) + } } -pub trait PropertyFilterOps: InternalPropertyFilterOps { - fn eq(&self, value: impl Into) -> PropertyFilter; - fn ne(&self, value: impl Into) -> PropertyFilter; - fn le(&self, value: impl Into) -> PropertyFilter; - fn ge(&self, value: impl Into) -> PropertyFilter; - fn lt(&self, value: impl Into) -> PropertyFilter; - fn gt(&self, value: impl Into) -> PropertyFilter; - fn is_in(&self, values: impl IntoIterator) -> PropertyFilter; - fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter; - fn is_none(&self) -> PropertyFilter; - fn is_some(&self) -> PropertyFilter; - fn starts_with(&self, value: impl Into) -> PropertyFilter; - fn ends_with(&self, value: impl Into) -> PropertyFilter; - fn contains(&self, value: impl Into) -> PropertyFilter; - fn not_contains(&self, value: impl Into) -> PropertyFilter; +pub trait PropertyFilterOps: InternalPropertyFilterBuilderOps { + fn eq(&self, value: impl Into) -> Self::Filter; + fn ne(&self, value: impl Into) -> Self::Filter; + fn le(&self, value: impl Into) -> Self::Filter; + fn ge(&self, value: impl Into) -> Self::Filter; + fn lt(&self, value: impl Into) -> Self::Filter; + fn gt(&self, value: impl Into) -> Self::Filter; + fn is_in(&self, values: impl IntoIterator) -> Self::Filter; + fn is_not_in(&self, values: impl IntoIterator) -> Self::Filter; + fn is_none(&self) -> Self::Filter; + fn is_some(&self) -> Self::Filter; + fn starts_with(&self, value: impl Into) -> Self::Filter; + fn ends_with(&self, value: impl Into) -> Self::Filter; + fn contains(&self, value: impl Into) -> Self::Filter; + fn not_contains(&self, value: impl Into) -> Self::Filter; fn fuzzy_search( &self, prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> PropertyFilter; + ) -> Self::Filter; } -impl PropertyFilterOps for T { - fn eq(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { +impl PropertyFilterOps for T { + fn eq(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Eq, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn ne(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ne(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Ne, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn le(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn le(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Le, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn ge(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ge(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Ge, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn lt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn lt(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Lt, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn gt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn gt(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Gt, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter { + fn is_in(&self, values: impl IntoIterator) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Set(Arc::new(values.into_iter().collect())), operator: FilterOperator::IsIn, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter { + fn is_not_in(&self, values: impl IntoIterator) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Set(Arc::new(values.into_iter().collect())), operator: FilterOperator::IsNotIn, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn is_none(&self) -> PropertyFilter { - PropertyFilter { + fn is_none(&self) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, operator: FilterOperator::IsNone, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn is_some(&self) -> PropertyFilter { - PropertyFilter { + fn is_some(&self) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, operator: FilterOperator::IsSome, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn starts_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn starts_with(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::StartsWith, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn ends_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ends_with(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::EndsWith, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn contains(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::Contains, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } - fn not_contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn not_contains(&self, value: impl Into) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), operator: FilterOperator::NotContains, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } fn fuzzy_search( @@ -1542,8 +1577,8 @@ impl PropertyFilterOps for T { prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> PropertyFilter { - PropertyFilter { + ) -> Self::Filter { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(Prop::Str(ArcStr::from(prop_value.into()))), operator: FilterOperator::FuzzySearch { @@ -1552,12 +1587,21 @@ impl PropertyFilterOps for T { }, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.filter(filter) } } #[derive(Clone)] -pub struct PropertyFilterBuilder(pub String, pub M); +pub struct PropertyFilterBuilder(String, pub M); + +impl Wrap for PropertyFilterBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} impl PropertyFilterBuilder { pub fn new(prop: impl Into, entity: M) -> Self { @@ -1565,35 +1609,83 @@ impl PropertyFilterBuilder { } } -impl InternalPropertyFilterOps for PropertyFilterBuilder { +impl InternalPropertyFilterBuilderOps for PropertyFilterBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, +{ + type Filter = PropertyFilter; + type Chained = OpChainBuilder; type Marker = M; + fn property_ref(&self) -> PropertyRef { PropertyRef::Property(self.0.clone()) } + fn ops(&self) -> &[Op] { + &[] + } + fn entity(&self) -> Self::Marker { self.1.clone() } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + filter + } + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + builder + } } #[derive(Clone)] pub struct MetadataFilterBuilder(pub String, pub M); +impl Wrap for MetadataFilterBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + impl MetadataFilterBuilder { pub fn new(prop: impl Into, entity: M) -> Self { Self(prop.into(), entity) } } -impl InternalPropertyFilterOps for MetadataFilterBuilder { +impl InternalPropertyFilterBuilderOps for MetadataFilterBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, +{ + type Filter = PropertyFilter; + type Chained = OpChainBuilder; type Marker = M; + fn property_ref(&self) -> PropertyRef { PropertyRef::Metadata(self.0.clone()) } + fn ops(&self) -> &[Op] { + &[] + } + fn entity(&self) -> Self::Marker { self.1.clone() } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + filter + } + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + builder + } } #[derive(Clone)] @@ -1603,6 +1695,14 @@ pub struct OpChainBuilder { pub entity: M, } +impl Wrap for OpChainBuilder { + type Wrapped = T; + + fn wrap(&self, value: T) -> Self::Wrapped { + value + } +} + impl OpChainBuilder { pub fn with_op(mut self, op: Op) -> Self { self.ops.push(op); @@ -1651,7 +1751,13 @@ impl OpChainBuilder { } } -impl InternalPropertyFilterOps for OpChainBuilder { +impl InternalPropertyFilterBuilderOps for OpChainBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, +{ + type Filter = PropertyFilter; + type Chained = OpChainBuilder; type Marker = M; fn property_ref(&self) -> PropertyRef { @@ -1665,33 +1771,43 @@ impl InternalPropertyFilterOps for OpChainBuil fn entity(&self) -> Self::Marker { self.entity.clone() } + + fn filter(&self, filter: PropertyFilter) -> Self::Filter { + filter + } + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained { + builder + } } -pub trait ElemQualifierOps: InternalPropertyFilterOps { - fn any(&self) -> OpChainBuilder +pub trait ElemQualifierOps: InternalPropertyFilterBuilderOps { + fn any(&self) -> Self::Chained where Self: Sized, { - OpChainBuilder { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Any]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn all(&self) -> OpChainBuilder + fn all(&self) -> Self::Chained where Self: Sized, { - OpChainBuilder { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::All]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } } -impl ElemQualifierOps for T {} +impl ElemQualifierOps for T {} impl PropertyFilterBuilder { pub fn temporal(self) -> OpChainBuilder { @@ -1703,104 +1819,69 @@ impl PropertyFilterBuilder { } } -pub trait ListAggOps: InternalPropertyFilterOps + Sized { - fn len(&self) -> OpChainBuilder { - OpChainBuilder { +pub trait ListAggOps: InternalPropertyFilterBuilderOps { + fn len(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Len]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn sum(&self) -> OpChainBuilder { - OpChainBuilder { + fn sum(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Sum]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn avg(&self) -> OpChainBuilder { - OpChainBuilder { + fn avg(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Avg]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn min(&self) -> OpChainBuilder { - OpChainBuilder { + fn min(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Min]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn max(&self) -> OpChainBuilder { - OpChainBuilder { + fn max(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Max]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn first(&self) -> OpChainBuilder { - OpChainBuilder { + fn first(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::First]).collect(), entity: self.entity(), - } + }; + self.chained(builder) } - fn last(&self) -> OpChainBuilder { - OpChainBuilder { + fn last(&self) -> Self::Chained { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Last]).collect(), entity: self.entity(), - } - } -} - -impl ListAggOps for T {} - -impl TryAsCompositeFilter for PropertyFilter> { - fn try_as_composite_node_filter(&self) -> Result { - Ok(CompositeNodeFilter::PropertyWindowed(self.clone())) - } - fn try_as_composite_edge_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) - } -} - -impl TryAsCompositeFilter for PropertyFilter> { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - fn try_as_composite_edge_filter(&self) -> Result { - Ok(CompositeEdgeFilter::PropertyWindowed(self.clone())) - } - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) + }; + self.chained(builder) } } -impl TryAsCompositeFilter for PropertyFilter> { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - fn try_as_composite_edge_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Ok(CompositeExplodedEdgeFilter::PropertyWindowed(self.clone())) - } -} +impl ListAggOps for T {} diff --git a/raphtory/src/db/graph/views/filter/node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs new file mode 100644 index 0000000000..bb941ad5a3 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs @@ -0,0 +1,89 @@ +use crate::{ + db::api::{ + properties::internal::InheritPropertiesOps, + state::ops::NodeFilterOp, + view::internal::{ + Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, + InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, + InheritTimeSemantics, InternalNodeFilterOps, Static, + }, + }, + prelude::GraphViewOps, +}; +use raphtory_api::{core::entities::LayerIds, inherit::Base}; +use raphtory_storage::{ + core_ops::InheritCoreGraphOps, + graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, +}; + +#[derive(Debug, Clone)] +pub struct NodeFilteredGraph { + graph: G, + filter: F, +} + +impl NodeFilteredGraph { + pub fn new(graph: G, filter: F) -> Self { + Self { graph, filter } + } +} + +impl Base for NodeFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for NodeFilteredGraph {} +impl Immutable for NodeFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritCoreGraphOps + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritStorageOps + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritLayerOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritListOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritMaterialize + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritAllEdgeFilterOps + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritPropertiesOps + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritTimeSemantics + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritNodeHistoryFilter + for NodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InheritEdgeHistoryFilter + for NodeFilteredGraph +{ +} + +impl<'graph, G: GraphViewOps<'graph>, F: NodeFilterOp> InternalNodeFilterOps + for NodeFilteredGraph +{ + fn internal_nodes_filtered(&self) -> bool { + true + } + + #[inline] + fn internal_filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + self.graph.internal_filter_node(node, layer_ids) + && self.filter.apply(self.graph.core_graph(), node.vid()) + } +} diff --git a/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs deleted file mode 100644 index fc73512aec..0000000000 --- a/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - db::{ - api::{ - properties::internal::InheritPropertiesOps, - view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::{ - internal::CreateFilter, - model::{ - node_filter::{NodeFilter, NodeIdFilter}, - Filter, - }, - }, - }, - errors::GraphError, - prelude::GraphViewOps, -}; -use raphtory_api::{core::entities::LayerIds, inherit::Base}; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, -}; - -#[derive(Debug, Clone)] -pub struct NodeIdFilteredGraph { - graph: G, - filter: Filter, -} - -impl NodeIdFilteredGraph { - pub(crate) fn new(graph: G, filter: Filter) -> Self { - Self { graph, filter } - } -} - -impl CreateFilter for NodeIdFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeIdFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - NodeFilter::validate(graph.id_type(), &self.0)?; - Ok(NodeIdFilteredGraph::new(graph, self.0)) - } -} - -impl Base for NodeIdFilteredGraph { - type Base = G; - - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl Static for NodeIdFilteredGraph {} -impl Immutable for NodeIdFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritAllEdgeFilterOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for NodeIdFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for NodeIdFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodeIdFilteredGraph { - fn internal_nodes_filtered(&self) -> bool { - true - } - - #[inline] - fn internal_filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { - self.graph.internal_filter_node(node, layer_ids) && self.filter.id_matches(node.id()) - } -} diff --git a/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs deleted file mode 100644 index 6a60cc5b8f..0000000000 --- a/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::{ - db::{ - api::{ - properties::internal::InheritPropertiesOps, - view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::{internal::CreateFilter, model::Filter, NodeNameFilter}, - }, - errors::GraphError, - prelude::GraphViewOps, -}; -use raphtory_api::{core::entities::LayerIds, inherit::Base}; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, -}; - -#[derive(Debug, Clone)] -pub struct NodeNameFilteredGraph { - graph: G, - filter: Filter, -} - -impl NodeNameFilteredGraph { - pub(crate) fn new(graph: G, filter: Filter) -> Self { - Self { graph, filter } - } -} - -impl CreateFilter for NodeNameFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeNameFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - Ok(NodeNameFilteredGraph::new(graph, self.0)) - } -} - -impl Base for NodeNameFilteredGraph { - type Base = G; - - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl Static for NodeNameFilteredGraph {} -impl Immutable for NodeNameFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritAllEdgeFilterOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for NodeNameFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for NodeNameFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodeNameFilteredGraph { - fn internal_nodes_filtered(&self) -> bool { - true - } - - #[inline] - fn internal_filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { - self.graph.internal_filter_node(node, layer_ids) - && self.filter.matches(Some(&node.id().to_str())) - } -} diff --git a/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs index e5a901bb08..61aa617513 100644 --- a/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs @@ -2,121 +2,31 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::NodeOp, view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, + GraphView, Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, InternalNodeFilterOps, Static, }, }, - graph::views::{ - filter::{ - internal::CreateFilter, - model::{node_filter::NodeFilter, property_filter::PropertyFilter, Windowed}, - }, - window_graph::WindowedGraph, - }, - }, - errors::GraphError, + graph::views::filter::internal::CreateFilter, + } + , prelude::{GraphViewOps, TimeOps}, }; use raphtory_api::{ - core::{entities::LayerIds, storage::timeindex::AsTime}, + core::storage::timeindex::AsTime, inherit::Base, }; -use raphtory_storage::{core_ops::InheritCoreGraphOps, graph::nodes::node_ref::NodeStorageRef}; - -#[derive(Debug, Clone)] -pub struct NodePropertyFilteredGraph { - graph: G, - prop_id: usize, - filter: PropertyFilter, -} - -impl NodePropertyFilteredGraph { - pub(crate) fn new(graph: G, prop_id: usize, filter: PropertyFilter) -> Self { - Self { - graph, - prop_id, - filter, - } - } -} - -impl CreateFilter for PropertyFilter> { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = - NodePropertyFilteredGraph>; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - let prop_id = self.resolve_prop_id(graph.node_meta(), false)?; - let filter = PropertyFilter { - prop_ref: self.prop_ref, - prop_value: self.prop_value, - operator: self.operator, - ops: self.ops, - entity: NodeFilter, - }; - Ok(NodePropertyFilteredGraph::new( - graph.window(self.entity.start.t(), self.entity.end.t()), - prop_id, - filter, - )) - } -} - -impl CreateFilter for PropertyFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - let prop_id = self.resolve_prop_id(graph.node_meta(), false)?; - Ok(NodePropertyFilteredGraph::new(graph, prop_id, self)) - } -} - -impl Base for NodePropertyFilteredGraph { - type Base = G; - - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl Static for NodePropertyFilteredGraph {} -impl Immutable for NodePropertyFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritAllEdgeFilterOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for NodePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for NodePropertyFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodePropertyFilteredGraph { - fn internal_nodes_filtered(&self) -> bool { - true - } - - #[inline] - fn internal_filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { - self.graph.internal_filter_node(node, layer_ids) - && self.filter.matches_node(&self.graph, self.prop_id, node) - } -} +use raphtory_storage::core_ops::CoreGraphOps; +use raphtory_storage::layer_ops::InternalLayerOps; +use raphtory_storage::core_ops::InheritCoreGraphOps; #[cfg(test)] mod test_node_property_filtered_graph { use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, IterFilterOps}, + api::view::{filter_ops::Filter, Select}, graph::{ assertions::assert_ok_or_missing_nodes, graph::assert_edges_equal, diff --git a/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs deleted file mode 100644 index 09d78fcd00..0000000000 --- a/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs +++ /dev/null @@ -1,780 +0,0 @@ -use crate::{ - core::entities::LayerIds, - db::{ - api::{ - properties::internal::InheritPropertiesOps, - view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::{internal::CreateFilter, NodeTypeFilter}, - }, - errors::GraphError, - prelude::GraphViewOps, -}; -use raphtory_api::inherit::Base; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, -}; -use std::sync::Arc; - -#[derive(Clone, Debug)] -pub struct NodeTypeFilteredGraph { - pub(crate) graph: G, - pub(crate) node_types_filter: Arc<[bool]>, -} - -impl Static for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> Base for NodeTypeFilteredGraph { - type Base = G; - #[inline(always)] - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl<'graph, G: GraphViewOps<'graph>> NodeTypeFilteredGraph { - pub fn new(graph: G, node_types_filter: Arc<[bool]>) -> Self { - Self { - graph, - node_types_filter, - } - } -} - -impl CreateFilter for NodeTypeFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeTypeFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - let node_types_filter = graph - .node_meta() - .node_type_meta() - .get_keys() - .iter() - .map(|k| self.0.matches(Some(k))) // TODO: _default check - .collect::>(); - Ok(NodeTypeFilteredGraph::new(graph, node_types_filter.into())) - } -} - -impl<'graph, G: GraphViewOps<'graph>> Immutable for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritAllEdgeFilterOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for NodeTypeFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodeTypeFilteredGraph { - fn internal_nodes_filtered(&self) -> bool { - true - } - - #[inline] - fn internal_filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { - self.node_types_filter - .get(node.node_type_id()) - .copied() - .unwrap_or(false) - && self.graph.internal_filter_node(node, layer_ids) - } -} - -#[cfg(test)] -mod tests_node_type_filtered_subgraph { - use crate::{ - db::{ - api::view::filter_ops::BaseFilterOps, - graph::{ - graph::assert_graph_equal, - views::filter::model::{ - edge_filter::EdgeFilter, node_filter::NodeFilter, - property_filter::PropertyFilterOps, PropertyFilterFactory, - }, - }, - }, - prelude::*, - test_utils::{build_graph, build_graph_strat, make_node_types}, - }; - use proptest::{arbitrary::any, proptest}; - use raphtory_storage::mutation::addition_ops::InternalAdditionOps; - use std::ops::Range; - - #[test] - fn test_type_filtered_subgraph() { - let graph = Graph::new(); - let edges = vec![ - (1, "A", "B", vec![("p1", 1u64)], None), - (2, "B", "C", vec![("p1", 2u64)], None), - (3, "C", "D", vec![("p1", 3u64)], None), - (4, "D", "E", vec![("p1", 4u64)], None), - ]; - - for (id, src, dst, props, layer) in &edges { - graph - .add_edge(*id, src, dst, props.clone(), *layer) - .unwrap(); - } - - let nodes = vec![ - (1, "A", vec![("p1", 1u64)], Some("water_tribe")), - (2, "B", vec![("p1", 2u64)], Some("water_tribe")), - (3, "C", vec![("p1", 1u64)], Some("fire_nation")), - (4, "D", vec![("p1", 1u64)], Some("air_nomads")), - ]; - - for (id, name, props, layer) in &nodes { - graph.add_node(*id, name, props.clone(), *layer).unwrap(); - } - - let type_filtered_subgraph = graph - .subgraph_node_types(vec!["fire_nation", "air_nomads"]) - .window(1, 5); - - assert_eq!(type_filtered_subgraph.nodes(), vec!["C", "D"]); - - assert_eq!( - type_filtered_subgraph - .clone() - .filter(NodeFilter.property("p1").eq(1u64)) - .unwrap() - .nodes(), - vec!["C", "D"] - ); - - assert!(type_filtered_subgraph - .filter(EdgeFilter.property("p1").eq(1u64)) - .unwrap() - .edges() - .is_empty()) - } - - #[test] - fn materialize_prop_test() { - proptest!(|(graph_f in build_graph_strat(10, 10, true), node_types in make_node_types())| { - let g = Graph::from(build_graph(&graph_f)).subgraph_node_types(node_types); - let gm = g.materialize().unwrap(); - assert_graph_equal(&g, &gm); - }) - } - - #[test] - fn materialize_type_window_prop_test() { - proptest!(|(graph_f in build_graph_strat(10, 10, true), w in any::>(), node_types in make_node_types())| { - let g = Graph::from(build_graph(&graph_f)).subgraph_node_types(node_types); - let gvw = g.window(w.start, w.end); - let gmw = gvw.materialize().unwrap(); - assert_graph_equal(&gvw, &gmw); - }) - } - - #[test] - fn materialize_window_type_prop_test() { - proptest!(|(graph_f in build_graph_strat(10, 10, true), w in any::>(), node_types in make_node_types())| { - let g = Graph::from(build_graph(&graph_f)); - let gvw = g.window(w.start, w.end).subgraph_node_types(node_types); - let gmw = gvw.materialize().unwrap(); - assert_graph_equal(&gvw, &gmw); - }) - } - - #[test] - fn node_removed_via_edge_removal() { - let g = Graph::new(); - g.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - g.node(1).unwrap().set_node_type("test").unwrap(); - let expected = Graph::new(); - expected.resolve_layer(None).unwrap(); - assert_graph_equal(&g.subgraph_node_types(["test"]), &expected); - } - - #[test] - fn node_removed_via_edge_removal_window() { - let g = Graph::new(); - g.add_edge(0, 0, 1, NO_PROPS, None).unwrap(); - g.node(0).unwrap().set_node_type("two").unwrap(); - let gw = g.window(0, 1); - let expected = Graph::new(); - expected.resolve_layer(None).unwrap(); - let sg = gw.subgraph_node_types(["_default"]); - assert!(!sg.has_node(0)); - assert!(!sg.has_node(1)); - assert_graph_equal(&sg, &expected); - assert_graph_equal(&sg, &sg.materialize().unwrap()) - } - mod test_filters_node_type_filtered_subgraph { - use crate::{ - db::{ - api::view::StaticGraphViewOps, - graph::{ - assertions::GraphTransformer, - views::{ - filter::node_type_filtered_graph::NodeTypeFilteredGraph, - layer_graph::LayeredGraph, window_graph::WindowedGraph, - }, - }, - }, - prelude::{GraphViewOps, LayerOps, NodeViewOps, TimeOps}, - }; - use std::ops::Range; - - fn get_all_node_types(graph: &G) -> Vec { - graph - .nodes() - .node_type() - .into_iter() - .flat_map(|(_, node_type)| node_type) - .map(|s| s.to_string()) - .collect() - } - - struct NodeTypeGraphTransformer(Option>); - - impl GraphTransformer for NodeTypeGraphTransformer { - type Return = NodeTypeFilteredGraph; - fn apply(&self, graph: G) -> Self::Return { - let node_types: Vec = - self.0.clone().unwrap_or_else(|| get_all_node_types(&graph)); - graph.subgraph_node_types(node_types) - } - } - - struct WindowedNodeTypeGraphTransformer(Option>, Range); - - impl GraphTransformer for WindowedNodeTypeGraphTransformer { - type Return = WindowedGraph>; - fn apply(&self, graph: G) -> Self::Return { - let node_types: Vec = - self.0.clone().unwrap_or_else(|| get_all_node_types(&graph)); - graph - .subgraph_node_types(node_types) - .window(self.1.start, self.1.end) - } - } - - struct LayeredNodeTypeGraphTransformer(Option>, Vec); - - impl GraphTransformer for LayeredNodeTypeGraphTransformer { - type Return = LayeredGraph>; - fn apply(&self, graph: G) -> Self::Return { - let node_types: Vec = - self.0.clone().unwrap_or_else(|| get_all_node_types(&graph)); - graph - .subgraph_node_types(node_types) - .layers(self.1.clone()) - .unwrap() - } - } - - struct LayeredWindowedNodeTypeGraphTransformer( - Option>, - Range, - Vec, - ); - - impl GraphTransformer for LayeredWindowedNodeTypeGraphTransformer { - type Return = - WindowedGraph>>; - fn apply(&self, graph: G) -> Self::Return { - let node_types: Vec = - self.0.clone().unwrap_or_else(|| get_all_node_types(&graph)); - graph - .subgraph_node_types(node_types) - .layers(self.2.clone()) - .unwrap() - .window(self.1.start, self.1.end) - } - } - - mod test_nodes_filters_node_type_filtered_subgraph { - use crate::{db::api::view::StaticGraphViewOps, prelude::AdditionOps}; - use raphtory_api::core::entities::properties::prop::Prop; - - fn init_graph(graph: G) -> G { - let nodes = vec![ - (6, "N1", vec![("p1", Prop::U64(2u64))], Some("air_nomad")), - (7, "N1", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (6, "N2", vec![("p1", Prop::U64(1u64))], Some("water_tribe")), - (7, "N2", vec![("p1", Prop::U64(2u64))], Some("water_tribe")), - (8, "N3", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (9, "N4", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (5, "N5", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (6, "N5", vec![("p1", Prop::U64(2u64))], Some("air_nomad")), - (5, "N6", vec![("p1", Prop::U64(1u64))], Some("fire_nation")), - (6, "N6", vec![("p1", Prop::U64(1u64))], Some("fire_nation")), - (3, "N7", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (5, "N7", vec![("p1", Prop::U64(1u64))], Some("air_nomad")), - (3, "N8", vec![("p1", Prop::U64(1u64))], Some("fire_nation")), - (4, "N8", vec![("p1", Prop::U64(2u64))], Some("fire_nation")), - ]; - - // Add nodes to the graph - for (id, name, props, layer) in &nodes { - graph.add_node(*id, name, props.clone(), *layer).unwrap(); - } - - graph - } - - use crate::db::graph::assertions::{ - assert_filter_nodes_results, assert_search_nodes_results, TestGraphVariants, - TestVariants, - }; - use crate::db::graph::views::filter::model::node_filter::NodeFilter; - use crate::db::graph::views::filter::model::property_filter::PropertyFilterOps; - use crate::db::graph::views::filter::model::PropertyFilterFactory; - use crate::db::graph::views::filter::node_type_filtered_graph::tests_node_type_filtered_subgraph::test_filters_node_type_filtered_subgraph::{NodeTypeGraphTransformer, WindowedNodeTypeGraphTransformer}; - - #[test] - fn test_nodes_filters() { - let filter = NodeFilter.property("p1").eq(1u64); - // NodePropertyFilter - let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; - assert_filter_nodes_results( - init_graph, - NodeTypeGraphTransformer(None), - filter.clone(), - &expected_results, - TestVariants::All, - ); - assert_search_nodes_results( - init_graph, - NodeTypeGraphTransformer(None), - filter, - &expected_results, - TestVariants::All, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = NodeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1", "N3", "N4", "N7"]; - assert_filter_nodes_results( - init_graph, - NodeTypeGraphTransformer(node_types.clone()), - filter.clone(), - &expected_results, - TestVariants::All, - ); - assert_search_nodes_results( - init_graph, - NodeTypeGraphTransformer(node_types), - filter, - &expected_results, - TestVariants::All, - ); - } - - #[test] - fn test_nodes_filters_w() { - // TODO: Enable event_disk_graph for filter_nodes once bug fixed: https://github.com/Pometry/Raphtory/issues/2098 - let filter = NodeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1", "N3", "N6"]; - assert_filter_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter.clone(), - &expected_results, - vec![TestGraphVariants::Graph], - ); - assert_search_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter, - &expected_results, - TestVariants::EventOnly, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = NodeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1", "N3"]; - assert_filter_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - vec![TestGraphVariants::Graph], - ); - assert_search_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types, 6..9), - filter, - &expected_results, - TestVariants::EventOnly, - ); - } - - #[test] - fn test_nodes_filters_pg_w() { - let filter = NodeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1", "N3", "N6", "N7"]; - assert_filter_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - assert_search_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter, - &expected_results, - TestVariants::PersistentOnly, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = NodeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1", "N3", "N7"]; - assert_filter_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - assert_search_nodes_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types, 6..9), - filter, - &expected_results, - TestVariants::PersistentOnly, - ); - } - } - - mod test_edges_filters_node_type_filtered_subgraph { - use crate::{ - db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, - graph::views::filter::model::property_filter::PropertyFilterOps, - }, - prelude::{AdditionOps, NO_PROPS}, - }; - use raphtory_api::core::entities::properties::prop::{Prop, PropUnwrap}; - use raphtory_storage::core_ops::CoreGraphOps; - - fn init_graph(graph: G) -> G { - let edges = vec![ - ( - 6, - "N1", - "N2", - vec![("p1", Prop::U64(2u64))], - Some("fire_nation"), - ), - (7, "N1", "N2", vec![("p1", Prop::U64(1u64))], None), - ( - 6, - "N2", - "N3", - vec![("p1", Prop::U64(1u64))], - Some("water_tribe"), - ), - ( - 7, - "N2", - "N3", - vec![("p1", Prop::U64(2u64))], - Some("water_tribe"), - ), - ( - 8, - "N3", - "N4", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - (9, "N4", "N5", vec![("p1", Prop::U64(1u64))], None), - ( - 5, - "N5", - "N6", - vec![("p1", Prop::U64(1u64))], - Some("air_nomad"), - ), - (6, "N5", "N6", vec![("p1", Prop::U64(2u64))], None), - ( - 5, - "N6", - "N7", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - ( - 6, - "N6", - "N7", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - ( - 3, - "N7", - "N8", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - (5, "N7", "N8", vec![("p1", Prop::U64(1u64))], None), - ( - 3, - "N8", - "N1", - vec![("p1", Prop::U64(1u64))], - Some("air_nomad"), - ), - ( - 4, - "N8", - "N1", - vec![("p1", Prop::U64(2u64))], - Some("water_tribe"), - ), - ]; - - for (id, src, dst, props, layer) in &edges { - graph - .add_edge(*id, src, dst, props.clone(), *layer) - .unwrap(); - } - - let nodes = vec![ - (6, "N1", NO_PROPS, Some("air_nomad")), - (6, "N2", NO_PROPS, Some("water_tribe")), - (8, "N3", NO_PROPS, Some("air_nomad")), - (9, "N4", NO_PROPS, Some("air_nomad")), - (5, "N5", NO_PROPS, Some("air_nomad")), - (5, "N6", NO_PROPS, Some("fire_nation")), - (3, "N7", NO_PROPS, Some("air_nomad")), - (4, "N8", NO_PROPS, Some("fire_nation")), - ]; - - for (id, name, props, layer) in &nodes { - graph.add_node(*id, name, props.clone(), *layer).unwrap(); - } - - graph - } - - use crate::db::graph::assertions::{assert_filter_edges_results, assert_search_edges_results, TestVariants}; - use crate::db::graph::views::filter::model::{PropertyFilterFactory}; - use crate::db::graph::views::filter::model::edge_filter::EdgeFilter; - use crate::db::graph::views::filter::node_type_filtered_graph::tests_node_type_filtered_subgraph::test_filters_node_type_filtered_subgraph::{get_all_node_types, LayeredNodeTypeGraphTransformer, LayeredWindowedNodeTypeGraphTransformer, NodeTypeGraphTransformer, WindowedNodeTypeGraphTransformer}; - use crate::prelude::{EdgeViewOps, Graph, GraphViewOps, PropertiesOps, TimeOps}; - - #[test] - fn test_edges_filters() { - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; - assert_filter_edges_results( - init_graph, - NodeTypeGraphTransformer(None), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - NodeTypeGraphTransformer(None), - filter, - &expected_results, - TestVariants::All, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4", "N4->N5"]; - assert_filter_edges_results( - init_graph, - NodeTypeGraphTransformer(node_types.clone()), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - NodeTypeGraphTransformer(node_types.clone()), - filter.clone(), - &expected_results, - TestVariants::All, - ); - - let layers = vec!["fire_nation".to_string()]; - let expected_results = vec!["N3->N4"]; - assert_filter_edges_results( - init_graph, - LayeredNodeTypeGraphTransformer(node_types.clone(), layers.clone()), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - LayeredNodeTypeGraphTransformer(node_types.clone(), layers), - filter, - &expected_results, - TestVariants::All, - ); - } - - #[test] - fn test_edges_filters_w() { - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; - assert_filter_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter, - &expected_results, - TestVariants::EventOnly, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4"]; - assert_filter_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - - let layers = vec!["fire_nation".to_string()]; - let expected_results = vec!["N3->N4"]; - assert_filter_edges_results( - init_graph, - LayeredWindowedNodeTypeGraphTransformer( - node_types.clone(), - 6..9, - layers.clone(), - ), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_graph, - LayeredWindowedNodeTypeGraphTransformer(node_types.clone(), 6..9, layers), - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - } - - #[test] - fn test_edges_filters_pg_w() { - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]; - let graph = init_graph(Graph::new()).persistent_graph(); - let edge = graph.edge("N8", "N1").unwrap(); - let graph = graph.window(6, 9); - let p = graph - .edge("N8", "N1") - .unwrap() - .properties() - .get("p1") - .unwrap_u64(); - assert_eq!(p, 2); - let r = graph - .filter(filter.clone()) - .unwrap() - .filter_edge(graph.core_edge(edge.edge.pid()).as_ref()); - assert!(!r); - assert_filter_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - assert_search_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(None, 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - - let node_types: Option> = - Some(vec!["air_nomad".into(), "water_tribe".into()]); - let filter = EdgeFilter.property("p1").eq(1u64); - let expected_results = vec!["N1->N2", "N3->N4"]; - assert_filter_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - assert_search_edges_results( - init_graph, - WindowedNodeTypeGraphTransformer(node_types.clone(), 6..9), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - - let layers = vec!["fire_nation".to_string()]; - let expected_results = vec!["N3->N4"]; - assert_filter_edges_results( - init_graph, - LayeredWindowedNodeTypeGraphTransformer( - node_types.clone(), - 6..9, - layers.clone(), - ), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - assert_search_edges_results( - init_graph, - LayeredWindowedNodeTypeGraphTransformer(node_types.clone(), 6..9, layers), - filter.clone(), - &expected_results, - TestVariants::PersistentOnly, - ); - } - } - } -} diff --git a/raphtory/src/db/graph/views/filter/not_filtered_graph.rs b/raphtory/src/db/graph/views/filter/not_filtered_graph.rs index 5c227132af..921da6166a 100644 --- a/raphtory/src/db/graph/views/filter/not_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/not_filtered_graph.rs @@ -2,6 +2,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::{filter::NotOp, NodeFilterOp}, view::internal::{ FilterOps, GraphView, Immutable, InheritEdgeHistoryFilter, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, @@ -38,6 +39,11 @@ impl CreateFilter for NotFilter { where Self: 'graph; + type NodeFilter<'graph, G: GraphView + 'graph> + = NotOp> + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -45,6 +51,13 @@ impl CreateFilter for NotFilter { let filter = self.0.create_filter(graph.clone())?; Ok(NotFilteredGraph { graph, filter }) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + Ok(self.0.create_node_filter(graph)?.not()) + } } impl Base for NotFilteredGraph { diff --git a/raphtory/src/db/graph/views/filter/or_filtered_graph.rs b/raphtory/src/db/graph/views/filter/or_filtered_graph.rs index 92dcfb98fa..5ae76740b7 100644 --- a/raphtory/src/db/graph/views/filter/or_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/or_filtered_graph.rs @@ -2,10 +2,12 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::{filter::OrOp, NodeFilterOp}, view::internal::{ - Immutable, InheritLayerOps, InheritListOps, InheritMaterialize, InheritStorageOps, - InheritTimeSemantics, InternalEdgeFilterOps, InternalEdgeLayerFilterOps, - InternalExplodedEdgeFilterOps, InternalNodeFilterOps, Static, + GraphView, Immutable, InheritLayerOps, InheritListOps, InheritMaterialize, + InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, + InternalEdgeLayerFilterOps, InternalExplodedEdgeFilterOps, InternalNodeFilterOps, + Static, }, }, graph::views::filter::{internal::CreateFilter, model::or_filter::OrFilter}, @@ -38,6 +40,11 @@ impl CreateFilter for OrFilter { where Self: 'graph; + type NodeFilter<'graph, G: GraphView + 'graph> + = OrOp, R::NodeFilter<'graph, G>> + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -46,6 +53,15 @@ impl CreateFilter for OrFilter { let right = self.right.create_filter(graph.clone())?; Ok(OrFilteredGraph { graph, left, right }) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + let left = self.left.create_node_filter(graph.clone())?; + let right = self.right.create_node_filter(graph.clone())?; + Ok(left.or(right)) + } } impl Base for OrFilteredGraph { diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 3ddc227fc8..a336fede7b 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -1161,7 +1161,7 @@ mod views_test { use crate::{ db::{ - api::view::filter_ops::BaseFilterOps, + api::view::filter_ops::Filter, graph::{ assertions::WindowGraphTransformer, views::filter::model::{ @@ -3292,21 +3292,23 @@ mod views_test { mod test_edges_filters_window_graph { use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, StaticGraphViewOps}, graph::{ assertions::{ assert_filter_edges_results, assert_search_edges_results, TestGraphVariants, TestVariants, WindowGraphTransformer, }, views::filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, - ComposableFilter, PropertyFilterFactory, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, ComposableFilter, + PropertyFilterFactory, }, }, }, errors::GraphError, - prelude::{AdditionOps, Graph, GraphViewOps, PropertyAdditionOps, TimeOps}, + prelude::{ + AdditionOps, Graph, GraphViewOps, PropertyAdditionOps, TimeOps, NO_PROPS, + }, }; use raphtory_api::core::{entities::properties::prop::Prop, storage::arc_str::ArcStr}; use std::sync::Arc; @@ -3525,6 +3527,10 @@ mod views_test { .unwrap(); } + graph.add_node(1, "N1", NO_PROPS, None).unwrap(); + graph.add_node(2, "N2", NO_PROPS, None).unwrap(); + graph.add_node(3, "N3", NO_PROPS, None).unwrap(); + graph } diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index 540cafdc98..ca0a1885e1 100644 --- a/raphtory/src/db/task/edge/eval_edge.rs +++ b/raphtory/src/db/task/edge/eval_edge.rs @@ -6,7 +6,7 @@ use crate::{ db::{ api::{ properties::Properties, - view::{internal::BaseFilter, *}, + view::{internal::InternalFilter, *}, }, graph::edge::EdgeView, task::{ @@ -137,17 +137,17 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S, CS: ComputeState + 'a> Clon } } -impl<'graph, 'a: 'graph, Current, S, CS> BaseFilter<'graph> +impl<'graph, 'a: 'graph, Current, S, CS> InternalFilter<'graph> for EvalEdgeView<'graph, 'a, Current, CS, S> where 'a: 'graph, Current: GraphViewOps<'graph>, CS: ComputeState + 'a, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = EvalEdgeView<'graph, 'a, Next, CS, S>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.edge.graph } diff --git a/raphtory/src/db/task/edge/eval_edges.rs b/raphtory/src/db/task/edge/eval_edges.rs index bba292b62f..c069d02f74 100644 --- a/raphtory/src/db/task/edge/eval_edges.rs +++ b/raphtory/src/db/task/edge/eval_edges.rs @@ -6,7 +6,7 @@ use crate::{ db::{ api::{ properties::{Metadata, Properties}, - view::{internal::BaseFilter, BaseEdgeViewOps, BoxedLIter}, + view::{internal::InternalFilter, BaseEdgeViewOps, BoxedLIter}, }, graph::edges::Edges, task::{ @@ -43,16 +43,16 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, CS: Clone, S> Clone } } -impl<'graph, 'a, Current, CS, S> BaseFilter<'graph> for EvalEdges<'graph, 'a, Current, CS, S> +impl<'graph, 'a, Current, CS, S> InternalFilter<'graph> for EvalEdges<'graph, 'a, Current, CS, S> where 'a: 'graph, Current: GraphViewOps<'graph>, CS: Clone, { - type BaseGraph = Current; + type Graph = Current; type Filtered + 'graph> = EvalEdges<'graph, 'a, Next, CS, S>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.edges.base_graph } diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs index 64e6ef18ba..f85fc5069f 100644 --- a/raphtory/src/db/task/node/eval_node.rs +++ b/raphtory/src/db/task/node/eval_node.rs @@ -12,7 +12,7 @@ use crate::{ api::{ state::NodeOp, view::{ - internal::{BaseFilter, GraphView}, + internal::{GraphView, InternalFilter}, BaseNodeViewOps, BoxedLIter, IntoDynBoxed, }, }, @@ -358,17 +358,18 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S: 'static, CS: ComputeState + } } -impl<'graph, 'a, S, CS, Current> BaseFilter<'graph> for EvalPathFromNode<'graph, 'a, Current, CS, S> +impl<'graph, 'a, S, CS, Current> InternalFilter<'graph> + for EvalPathFromNode<'graph, 'a, Current, CS, S> where 'a: 'graph, Current: GraphViewOps<'graph>, CS: ComputeState + 'a, S: 'static, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = EvalPathFromNode<'graph, 'a, Next, CS, S>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.eval_graph.base_graph } @@ -383,17 +384,17 @@ where } } -impl<'graph, 'a, Current, S, CS> BaseFilter<'graph> for EvalNodeView<'graph, 'a, Current, S, CS> +impl<'graph, 'a, Current, S, CS> InternalFilter<'graph> for EvalNodeView<'graph, 'a, Current, S, CS> where 'a: 'graph, Current: GraphViewOps<'graph>, CS: ComputeState + 'a, S: 'static, { - type BaseGraph = Current; + type Graph = Current; type Filtered> = EvalNodeView<'graph, 'a, Next, S, CS>; - fn base_graph(&self) -> &Self::BaseGraph { + fn base_graph(&self) -> &Self::Graph { &self.eval_graph.base_graph } diff --git a/raphtory/src/errors.rs b/raphtory/src/errors.rs index 651679c897..255422c096 100644 --- a/raphtory/src/errors.rs +++ b/raphtory/src/errors.rs @@ -374,6 +374,9 @@ pub enum GraphError { #[error("Not supported")] NotSupported, + #[error("Node filter expected")] + NotNodeFilter, + #[error("Operator {0} requires a property value, but none was provided.")] InvalidFilterExpectSingleGotNone(FilterOperator), diff --git a/raphtory/src/python/filter/create_filter.rs b/raphtory/src/python/filter/create_filter.rs index 327deb21cd..b7af3625f8 100644 --- a/raphtory/src/python/filter/create_filter.rs +++ b/raphtory/src/python/filter/create_filter.rs @@ -1,6 +1,9 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::NodeOp, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{internal::CreateFilter, model::TryAsCompositeFilter}, }, errors::GraphError, @@ -13,6 +16,11 @@ pub trait DynInternalFilterOps: Send + Sync + TryAsCompositeFilter { &self, graph: Arc, ) -> Result, GraphError>; + + fn create_dyn_node_filter<'graph>( + &self, + graph: Arc, + ) -> Result + 'graph>, GraphError>; } impl DynInternalFilterOps for T @@ -25,6 +33,13 @@ where ) -> Result, GraphError> { Ok(Arc::new(self.clone().create_filter(graph)?)) } + + fn create_dyn_node_filter<'graph>( + &self, + graph: Arc, + ) -> Result + 'graph>, GraphError> { + Ok(Arc::new(self.clone().create_node_filter(graph)?)) + } } impl CreateFilter for Arc { @@ -33,10 +48,19 @@ impl CreateFilter for Arc { where Self: 'graph; + type NodeFilter<'graph, G: GraphView + 'graph> = Arc + 'graph>; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { self.deref().create_dyn_filter(Arc::new(graph)) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.deref().create_dyn_node_filter(Arc::new(graph)) + } } diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 8c95e16e19..06cf52691a 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -1,28 +1,32 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{ - EdgeEndpointFilter, EdgeFilter, EdgeFilterOps, EdgeIdFilterBuilder, ExplodedEdgeFilter, - InternalEdgeFilterBuilderOps, + edge_filter::{EdgeFilter, EndpointWrapper}, + node_filter::{ + NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, NodeIdFilterBuilderOps, + NodeNameFilterBuilder, NodeTypeFilterBuilder, }, - property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, + property_filter::{MetadataFilterBuilder, PropertyFilterBuilder, PropertyFilterOps}, PropertyFilterFactory, Windowed, }, + prelude::TimeOps, python::{ filter::{ filter_expr::PyFilterExpr, - window_filter::{PyEdgeWindow, PyExplodedEdgeWindow}, + property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, + }, }, types::iterable::FromIterable, utils::PyTime, }, }; -use pyo3::{pyclass, pymethods, PyResult}; +use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyResult, Python}; use raphtory_api::core::entities::GID; use std::sync::Arc; #[pyclass(frozen, name = "EdgeIdFilterOp", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeIdFilterOp(pub EdgeIdFilterBuilder); +pub struct PyEdgeIdFilterOp(pub EndpointWrapper); #[pymethods] impl PyEdgeIdFilterOp { @@ -90,54 +94,98 @@ impl PyEdgeIdFilterOp { #[pyclass(frozen, name = "EdgeFilterOp", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeFilterOp(Arc); +pub struct PyEdgeFilterOp(EdgeTextBuilder); -impl From for PyEdgeFilterOp { - fn from(value: T) -> Self { - PyEdgeFilterOp(Arc::new(value)) +#[derive(Clone)] +enum EdgeTextBuilder { + Name(EndpointWrapper), + Type(EndpointWrapper), +} + +impl From> for PyEdgeFilterOp { + fn from(v: EndpointWrapper) -> Self { + PyEdgeFilterOp(EdgeTextBuilder::Name(v)) + } +} + +impl From> for PyEdgeFilterOp { + fn from(v: EndpointWrapper) -> Self { + PyEdgeFilterOp(EdgeTextBuilder::Type(v)) + } +} + +impl PyEdgeFilterOp { + #[inline] + fn map( + &self, + f_name: impl FnOnce(&EndpointWrapper) -> T, + f_type: impl FnOnce(&EndpointWrapper) -> T, + ) -> T { + match &self.0 { + EdgeTextBuilder::Name(n) => f_name(n), + EdgeTextBuilder::Type(t) => f_type(t), + } } } #[pymethods] impl PyEdgeFilterOp { fn __eq__(&self, value: String) -> PyFilterExpr { - let field = self.0.eq(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.eq(value.clone()))), + |t| PyFilterExpr(Arc::new(t.eq(value.clone()))), + ) } fn __ne__(&self, value: String) -> PyFilterExpr { - let field = self.0.ne(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.ne(value.clone()))), + |t| PyFilterExpr(Arc::new(t.ne(value.clone()))), + ) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - let field = self.0.is_in(values); - PyFilterExpr(Arc::new(field)) + let vals: Vec = values.into_iter().collect(); + self.map( + |n| PyFilterExpr(Arc::new(n.is_in(vals.clone()))), + |t| PyFilterExpr(Arc::new(t.is_in(vals.clone()))), + ) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - let field = self.0.is_not_in(values); - PyFilterExpr(Arc::new(field)) + let vals: Vec = values.into_iter().collect(); + self.map( + |n| PyFilterExpr(Arc::new(n.is_not_in(vals.clone()))), + |t| PyFilterExpr(Arc::new(t.is_not_in(vals.clone()))), + ) } fn starts_with(&self, value: String) -> PyFilterExpr { - let field = self.0.starts_with(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.starts_with(value.clone()))), + |t| PyFilterExpr(Arc::new(t.starts_with(value.clone()))), + ) } fn ends_with(&self, value: String) -> PyFilterExpr { - let field = self.0.ends_with(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.ends_with(value.clone()))), + |t| PyFilterExpr(Arc::new(t.ends_with(value.clone()))), + ) } fn contains(&self, value: String) -> PyFilterExpr { - let field = self.0.contains(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.contains(value.clone()))), + |t| PyFilterExpr(Arc::new(t.contains(value.clone()))), + ) } fn not_contains(&self, value: String) -> PyFilterExpr { - let field = self.0.not_contains(value); - PyFilterExpr(Arc::new(field)) + self.map( + |n| PyFilterExpr(Arc::new(n.not_contains(value.clone()))), + |t| PyFilterExpr(Arc::new(t.not_contains(value.clone()))), + ) } fn fuzzy_search( @@ -146,16 +194,28 @@ impl PyEdgeFilterOp { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - let field = self - .0 - .fuzzy_search(value, levenshtein_distance, prefix_match); - PyFilterExpr(Arc::new(field)) + self.map( + |n| { + PyFilterExpr(Arc::new(n.fuzzy_search( + value.clone(), + levenshtein_distance, + prefix_match, + ))) + }, + |t| { + PyFilterExpr(Arc::new(t.fuzzy_search( + value.clone(), + levenshtein_distance, + prefix_match, + ))) + }, + ) } } #[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeEndpoint(pub EdgeEndpointFilter); +pub struct PyEdgeEndpoint(pub EndpointWrapper); #[pymethods] impl PyEdgeEndpoint { @@ -164,7 +224,25 @@ impl PyEdgeEndpoint { } fn name(&self) -> PyEdgeFilterOp { - PyEdgeFilterOp(self.0.name()) + PyEdgeFilterOp::from(self.0.name()) + } + + fn node_type(&self) -> PyEdgeFilterOp { + PyEdgeFilterOp::from(self.0.node_type()) + } + + fn property<'py>( + &self, + py: Python<'py>, + name: String, + ) -> PyResult> { + let b = PropertyFilterFactory::property(&self.0, name); + b.into_pyobject(py) + } + + fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { + let b = PropertyFilterFactory::metadata(&self.0, name); + b.into_pyobject(py) } } @@ -185,43 +263,24 @@ impl PyEdgeFilter { } #[staticmethod] - fn property(name: String) -> PropertyFilterBuilder { - EdgeFilter.property(name) - } - - #[staticmethod] - fn metadata(name: String) -> MetadataFilterBuilder { - EdgeFilter.metadata(name) - } - - #[staticmethod] - fn window(py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyEdgeWindow(Windowed::::from_times( - py_start, py_end, - ))) - } -} - -#[pyclass(frozen, name = "ExplodedEdge", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyExplodedEdgeFilter; - -#[pymethods] -impl PyExplodedEdgeFilter { - #[staticmethod] - fn property(name: String) -> PropertyFilterBuilder { - ExplodedEdgeFilter.property(name) + fn property<'py>( + py: Python<'py>, + name: String, + ) -> PyResult> { + let b: PropertyFilterBuilder = + PropertyFilterFactory::property(&EdgeFilter, name); + b.into_pyobject(py) } #[staticmethod] - fn metadata(name: String) -> MetadataFilterBuilder { - ExplodedEdgeFilter.metadata(name) + fn metadata<'py>(py: Python<'py>, name: String) -> PyResult> { + let b: MetadataFilterBuilder = + PropertyFilterFactory::metadata(&EdgeFilter, name); + b.into_pyobject(py) } #[staticmethod] - fn window(py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyExplodedEdgeWindow( - Windowed::::from_times(py_start, py_end), - )) + fn window(start: PyTime, end: PyTime) -> PyPropertyFilterFactory { + PyPropertyFilterFactory::wrap(EdgeFilter::window(start, end)) } } diff --git a/raphtory/src/python/filter/exploded_edge_filter_builder.rs b/raphtory/src/python/filter/exploded_edge_filter_builder.rs new file mode 100644 index 0000000000..1a6db02b11 --- /dev/null +++ b/raphtory/src/python/filter/exploded_edge_filter_builder.rs @@ -0,0 +1,43 @@ +use crate::{ + db::graph::views::filter::model::{ + exploded_edge_filter::ExplodedEdgeFilter, + property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, + PropertyFilterFactory, Windowed, + }, + python::{ + filter::property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, + }, + utils::PyTime, + }, +}; +use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyResult, Python}; + +#[pyclass(frozen, name = "ExplodedEdge", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyExplodedEdgeFilter; + +#[pymethods] +impl PyExplodedEdgeFilter { + #[staticmethod] + fn property<'py>( + py: Python<'py>, + name: String, + ) -> PyResult> { + let b: PropertyFilterBuilder = + PropertyFilterFactory::property(&ExplodedEdgeFilter, name); + b.into_pyobject(py) + } + + #[staticmethod] + fn metadata<'py>(py: Python<'py>, name: String) -> PyResult> { + let b: MetadataFilterBuilder = + PropertyFilterFactory::metadata(&ExplodedEdgeFilter, name); + b.into_pyobject(py) + } + + #[staticmethod] + fn window(start: PyTime, end: PyTime) -> PyPropertyFilterFactory { + PyPropertyFilterFactory::wrap(ExplodedEdgeFilter::window(start, end)) + } +} diff --git a/raphtory/src/python/filter/filter_expr.rs b/raphtory/src/python/filter/filter_expr.rs index 75b3a32469..a9d1a401ed 100644 --- a/raphtory/src/python/filter/filter_expr.rs +++ b/raphtory/src/python/filter/filter_expr.rs @@ -1,6 +1,9 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::NodeOp, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ internal::CreateFilter, model::{ @@ -16,7 +19,7 @@ use crate::{ use pyo3::prelude::*; use std::sync::Arc; -#[pyclass(frozen, name = "FilterExpr", module = "raphtory.filter")] +#[pyclass(frozen, name = "FilterExpr", module = "raphtory.filter", subclass)] #[derive(Clone)] pub struct PyFilterExpr(pub Arc); @@ -50,8 +53,10 @@ impl PyFilterExpr { } impl CreateFilter for PyFilterExpr { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> - = Arc + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + + type NodeFilter<'graph, G: GraphView + 'graph> + = Arc + 'graph> where Self: 'graph; @@ -61,4 +66,11 @@ impl CreateFilter for PyFilterExpr { ) -> Result, GraphError> { self.0.create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.0.create_node_filter(graph) + } } diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 1aa6d6e80c..571c589c49 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -1,9 +1,9 @@ use crate::python::filter::{ - edge_filter_builders::{PyEdgeEndpoint, PyEdgeFilter, PyEdgeFilterOp, PyExplodedEdgeFilter}, + edge_filter_builders::{PyEdgeEndpoint, PyEdgeFilter, PyEdgeFilterOp, PyEdgeIdFilterOp}, + exploded_edge_filter_builder::PyExplodedEdgeFilter, filter_expr::PyFilterExpr, node_filter_builders::PyNodeFilter, property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, - window_filter::{PyEdgeWindow, PyExplodedEdgeWindow, PyNodeWindow}, }; use pyo3::{ prelude::{PyModule, PyModuleMethods}, @@ -12,25 +12,26 @@ use pyo3::{ mod create_filter; pub mod edge_filter_builders; +mod exploded_edge_filter_builder; pub mod filter_expr; pub mod node_filter_builders; pub mod property_filter_builders; -pub mod window_filter; pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { let filter_module = PyModule::new(py, "filter")?; + filter_module.add_class::()?; + filter_module.add_class::()?; filter_module.add_class::()?; + filter_module.add_class::()?; - filter_module.add_class::()?; - filter_module.add_class::()?; + filter_module.add_class::()?; + filter_module.add_class::()?; + filter_module.add_class::()?; + filter_module.add_class::()?; + filter_module.add_class::()?; - filter_module.add_class::()?; - filter_module.add_class::()?; - filter_module.add_class::()?; - filter_module.add_class::()?; - filter_module.add_class::()?; Ok(filter_module) } diff --git a/raphtory/src/python/filter/node_filter_builders.rs b/raphtory/src/python/filter/node_filter_builders.rs index 9714387de7..cdacb16d77 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -2,75 +2,26 @@ use crate::{ db::graph::views::filter::model::{ node_filter::{ InternalNodeFilterBuilderOps, NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, + NodeIdFilterBuilderOps, NodeNameFilterBuilder, NodeTypeFilterBuilder, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, }, python::{ - filter::{filter_expr::PyFilterExpr, window_filter::PyNodeWindow}, + filter::{ + filter_expr::PyFilterExpr, + property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, + }, + }, types::iterable::FromIterable, utils::PyTime, }, }; -use pyo3::{pyclass, pymethods, PyResult}; +use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyResult, Python}; use raphtory_api::core::entities::GID; use std::sync::Arc; -#[pyclass(frozen, name = "NodeFilterOp", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyNodeFilterBuilder(Arc); - -impl From for PyNodeFilterBuilder { - fn from(value: T) -> Self { - PyNodeFilterBuilder(Arc::new(value)) - } -} - -#[pymethods] -impl PyNodeFilterBuilder { - fn __eq__(&self, value: String) -> PyFilterExpr { - self.0.eq(value) - } - - fn __ne__(&self, value: String) -> PyFilterExpr { - self.0.ne(value) - } - - fn is_in(&self, values: FromIterable) -> PyFilterExpr { - self.0.is_in(values.into()) - } - - fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - self.0.is_not_in(values.into()) - } - - fn starts_with(&self, value: String) -> PyFilterExpr { - self.0.starts_with(value) - } - - fn ends_with(&self, value: String) -> PyFilterExpr { - self.0.ends_with(value) - } - - fn contains(&self, value: String) -> PyFilterExpr { - self.0.contains(value) - } - - fn not_contains(&self, value: String) -> PyFilterExpr { - self.0.not_contains(value) - } - - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - self.0 - .fuzzy_search(value, levenshtein_distance, prefix_match) - } -} - #[pyclass(frozen, name = "NodeIdFilterOp", module = "raphtory.filter")] #[derive(Clone)] pub struct PyIdNodeFilterBuilder(Arc); @@ -140,115 +91,119 @@ impl PyIdNodeFilterBuilder { } #[derive(Clone)] -#[pyclass(frozen, name = "Node", module = "raphtory.filter")] -pub struct PyNodeFilter; - -#[pymethods] -impl PyNodeFilter { - /// Filter node by id - /// - /// Returns: - /// NodeFilterBuilder: A filter builder for filtering by node id - #[staticmethod] - fn id() -> PyIdNodeFilterBuilder { - PyIdNodeFilterBuilder(Arc::new(NodeFilter::id())) - } - - /// Filter node by name - /// - /// Returns: - /// NodeFilterBuilder: A filter builder for filtering by node name - #[staticmethod] - fn name() -> PyNodeFilterBuilder { - PyNodeFilterBuilder(Arc::new(NodeFilter::name())) - } - - /// Filter node by type - /// - /// Returns: - /// NodeFilterBuilder: A filter builder for filtering by node type - #[staticmethod] - fn node_type() -> PyNodeFilterBuilder { - PyNodeFilterBuilder(Arc::new(NodeFilter::node_type())) - } +enum NodeTextBuilder { + Name(NodeNameFilterBuilder), + Type(NodeTypeFilterBuilder), +} - #[staticmethod] - fn property(name: String) -> PropertyFilterBuilder { - NodeFilter.property(name) - } +#[pyclass(frozen, name = "NodeFilterOp", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyNodeFilterOp(NodeTextBuilder); - #[staticmethod] - fn metadata(name: String) -> MetadataFilterBuilder { - NodeFilter.metadata(name) +impl From for PyNodeFilterOp { + fn from(v: NodeNameFilterBuilder) -> Self { + PyNodeFilterOp(NodeTextBuilder::Name(v)) } +} - #[staticmethod] - fn window(py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyNodeWindow(Windowed::::from_times( - py_start, py_end, - ))) +impl From for PyNodeFilterOp { + fn from(v: NodeTypeFilterBuilder) -> Self { + PyNodeFilterOp(NodeTextBuilder::Type(v)) } } -pub trait DynNodeFilterBuilderOps: Send + Sync { - fn eq(&self, value: String) -> PyFilterExpr; - - fn ne(&self, value: String) -> PyFilterExpr; - - fn is_in(&self, values: Vec) -> PyFilterExpr; - - fn is_not_in(&self, values: Vec) -> PyFilterExpr; - - fn starts_with(&self, value: String) -> PyFilterExpr; - - fn ends_with(&self, value: String) -> PyFilterExpr; - - fn contains(&self, value: String) -> PyFilterExpr; - - fn not_contains(&self, value: String) -> PyFilterExpr; - - fn fuzzy_search( +impl PyNodeFilterOp { + #[inline] + fn map( &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr; + f_name: impl FnOnce(&NodeNameFilterBuilder) -> T, + f_type: impl FnOnce(&NodeTypeFilterBuilder) -> T, + ) -> T { + match &self.0 { + NodeTextBuilder::Name(n) => f_name(n), + NodeTextBuilder::Type(t) => f_type(t), + } + } } -impl DynNodeFilterBuilderOps for T -where - T: InternalNodeFilterBuilderOps, -{ - fn eq(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::eq(self, value))) +#[pymethods] +impl PyNodeFilterOp { + fn __eq__(&self, value: String) -> PyFilterExpr { + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::eq(n, value.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::eq(t, value.clone()))), + ) } - fn ne(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::ne(self, value))) + fn __ne__(&self, value: String) -> PyFilterExpr { + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::ne(n, value.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::ne(t, value.clone()))), + ) } - fn is_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_in(self, values))) + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + let vals: Vec = values.into_iter().collect(); + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_in(n, vals.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_in(t, vals.clone()))), + ) } - fn is_not_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_not_in(self, values))) + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + let vals: Vec = values.into_iter().collect(); + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_not_in(n, vals.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::is_not_in(t, vals.clone()))), + ) } fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::starts_with(self, value))) + self.map( + |n| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::starts_with( + n, + value.clone(), + ))) + }, + |t| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::starts_with( + t, + value.clone(), + ))) + }, + ) } fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::ends_with(self, value))) + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::ends_with(n, value.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::ends_with(t, value.clone()))), + ) } fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::contains(self, value))) + self.map( + |n| PyFilterExpr(Arc::new(NodeFilterBuilderOps::contains(n, value.clone()))), + |t| PyFilterExpr(Arc::new(NodeFilterBuilderOps::contains(t, value.clone()))), + ) } fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::not_contains(self, value))) + self.map( + |n| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::not_contains( + n, + value.clone(), + ))) + }, + |t| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::not_contains( + t, + value.clone(), + ))) + }, + ) } fn fuzzy_search( @@ -257,11 +212,67 @@ where levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(NodeFilterBuilderOps::fuzzy_search( - self, - value, - levenshtein_distance, - prefix_match, - ))) + self.map( + |n| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::fuzzy_search( + n, + value.clone(), + levenshtein_distance, + prefix_match, + ))) + }, + |t| { + PyFilterExpr(Arc::new(NodeFilterBuilderOps::fuzzy_search( + t, + value.clone(), + levenshtein_distance, + prefix_match, + ))) + }, + ) + } +} + +#[pyclass(frozen, name = "Node", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyNodeFilter; + +#[pymethods] +impl PyNodeFilter { + #[staticmethod] + fn id() -> PyIdNodeFilterBuilder { + PyIdNodeFilterBuilder(Arc::new(NodeFilter::id())) + } + + #[staticmethod] + fn name() -> PyNodeFilterOp { + PyNodeFilterOp::from(NodeFilter::name()) + } + + #[staticmethod] + fn node_type() -> PyNodeFilterOp { + PyNodeFilterOp::from(NodeFilter::node_type()) + } + + #[staticmethod] + fn property<'py>( + py: Python<'py>, + name: String, + ) -> PyResult> { + let b: PropertyFilterBuilder = + PropertyFilterFactory::property(&NodeFilter, name); + b.into_pyobject(py) + } + + #[staticmethod] + fn metadata<'py>(py: Python<'py>, name: String) -> PyResult> { + let b: MetadataFilterBuilder = + PropertyFilterFactory::metadata(&NodeFilter, name); + b.into_pyobject(py) + } + + #[staticmethod] + fn window(start: PyTime, end: PyTime) -> PyPropertyFilterFactory { + PyPropertyFilterFactory::wrap(NodeFilter::window(start, end)) } } diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index 17768dd538..25a15ef226 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -2,21 +2,22 @@ use crate::{ db::graph::views::filter::{ internal::CreateFilter, model::{ + edge_filter::EndpointWrapper, property_filter::{ - ElemQualifierOps, ListAggOps, MetadataFilterBuilder, PropertyFilterBuilder, - PropertyFilterOps, + ElemQualifierOps, InternalPropertyFilterBuilderOps, ListAggOps, + MetadataFilterBuilder, OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, }, - TryAsCompositeFilter, + PropertyFilterFactory, TemporalPropertyFilterFactory, TryAsCompositeFilter, }, }, prelude::PropertyFilter, python::{filter::filter_expr::PyFilterExpr, types::iterable::FromIterable}, }; -use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyErr, PyResult, Python}; +use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyErr, Python}; use raphtory_api::core::entities::properties::prop::Prop; use std::sync::Arc; -pub trait DynFilterOps: Send + Sync { +pub trait DynPropertyFilterOps: Send + Sync { fn __eq__(&self, value: Prop) -> PyFilterExpr; fn __ne__(&self, value: Prop) -> PyFilterExpr; @@ -51,31 +52,29 @@ pub trait DynFilterOps: Send + Sync { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr; +} - fn any(&self) -> PyResult; +pub trait DynListFilterOps: Send + Sync { + fn any(&self) -> PyFilterOps; - fn all(&self) -> PyResult; + fn all(&self) -> PyFilterOps; - fn len(&self) -> PyResult; + fn len(&self) -> PyFilterOps; - fn sum(&self) -> PyResult; + fn sum(&self) -> PyFilterOps; - fn avg(&self) -> PyResult; + fn avg(&self) -> PyFilterOps; - fn min(&self) -> PyResult; + fn min(&self) -> PyFilterOps; - fn max(&self) -> PyResult; + fn max(&self) -> PyFilterOps; - fn first(&self) -> PyResult; + fn first(&self) -> PyFilterOps; - fn last(&self) -> PyResult; + fn last(&self) -> PyFilterOps; } -impl DynFilterOps for T -where - T: PropertyFilterOps + ElemQualifierOps + ListAggOps + Clone + Send + Sync + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ +impl DynPropertyFilterOps for T { fn __eq__(&self, value: Prop) -> PyFilterExpr { PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } @@ -97,7 +96,8 @@ where } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + let filter = Arc::new(PropertyFilterOps::ge(self, value)); + PyFilterExpr(filter) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { @@ -145,44 +145,54 @@ where prefix_match, ))) } +} - fn any(&self) -> PyResult { - Ok(PyFilterOps::wrap(ElemQualifierOps::any(self))) +impl DynListFilterOps for T +where + T: InternalPropertyFilterBuilderOps + 'static, +{ + fn any(&self) -> PyFilterOps { + let filter = ElemQualifierOps::any(self); + PyFilterOps::wrap(filter) } - fn all(&self) -> PyResult { - Ok(PyFilterOps::wrap(ElemQualifierOps::all(self))) + fn all(&self) -> PyFilterOps { + PyFilterOps::wrap(ElemQualifierOps::all(self)) } - fn len(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::len(self))) + fn len(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::len(self)) } - fn sum(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::sum(self))) + fn sum(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::sum(self)) } - fn avg(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::avg(self))) + fn avg(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::avg(self)) } - fn min(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::min(self))) + fn min(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::min(self)) } - fn max(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::max(self))) + fn max(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::max(self)) } - fn first(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::first(self))) + fn first(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::first(self)) } - fn last(&self) -> PyResult { - Ok(PyFilterOps::wrap(ListAggOps::last(self))) + fn last(&self) -> PyFilterOps { + PyFilterOps::wrap(ListAggOps::last(self)) } } +pub trait DynFilterOps: DynListFilterOps + DynPropertyFilterOps {} + +impl DynFilterOps for T {} + #[pyclass(frozen, name = "FilterOps", module = "raphtory.filter", subclass)] #[derive(Clone)] pub struct PyFilterOps { @@ -190,7 +200,7 @@ pub struct PyFilterOps { } impl PyFilterOps { - fn wrap(t: T) -> Self { + pub fn wrap(t: T) -> Self { Self { ops: Arc::new(t) } } @@ -267,39 +277,39 @@ impl PyFilterOps { .fuzzy_search(prop_value, levenshtein_distance, prefix_match) } - pub fn first(&self) -> PyResult { + pub fn first(&self) -> PyFilterOps { self.ops.first() } - pub fn last(&self) -> PyResult { + pub fn last(&self) -> PyFilterOps { self.ops.last() } - pub fn any(&self) -> PyResult { + pub fn any(&self) -> PyFilterOps { self.ops.any() } - pub fn all(&self) -> PyResult { + pub fn all(&self) -> PyFilterOps { self.ops.all() } - fn len(&self) -> PyResult { + fn len(&self) -> PyFilterOps { self.ops.len() } - fn sum(&self) -> PyResult { + fn sum(&self) -> PyFilterOps { self.ops.sum() } - fn avg(&self) -> PyResult { + fn avg(&self) -> PyFilterOps { self.ops.avg() } - fn min(&self) -> PyResult { + fn min(&self) -> PyFilterOps { self.ops.min() } - fn max(&self) -> PyResult { + fn max(&self) -> PyFilterOps { self.ops.max() } } @@ -325,13 +335,12 @@ pub trait DynPropertyFilterBuilderOps: DynFilterOps { fn temporal(&self) -> PyFilterOps; } -impl DynPropertyFilterBuilderOps for PropertyFilterBuilder +impl DynPropertyFilterBuilderOps for T where - PropertyFilter: CreateFilter + TryAsCompositeFilter, - T: Clone + Send + Sync + 'static, + T::Chained: 'static, { fn temporal(&self) -> PyFilterOps { - PyFilterOps::wrap(self.clone().temporal()) + PyFilterOps::wrap(self.temporal()) } } @@ -345,6 +354,7 @@ impl PyPropertyFilterBuilder { impl<'py, M: Clone + Send + Sync + 'static> IntoPyObject<'py> for PropertyFilterBuilder where PropertyFilter: CreateFilter + TryAsCompositeFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, { type Target = PyPropertyFilterBuilder; type Output = Bound<'py, Self::Target>; @@ -361,6 +371,7 @@ where impl<'py, M: Send + Sync + Clone + 'static> IntoPyObject<'py> for MetadataFilterBuilder where PropertyFilter: CreateFilter + TryAsCompositeFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, { type Target = PyFilterOps; type Output = Bound<'py, Self::Target>; @@ -370,3 +381,89 @@ where PyFilterOps::wrap(self).into_pyobject(py) } } + +impl<'py, M> IntoPyObject<'py> for EndpointWrapper> +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, +{ + type Target = PyPropertyFilterBuilder; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let inner: Arc>> = Arc::new(self); + let child = PyPropertyFilterBuilder::from_arc(inner.clone()); + let parent = PyFilterOps::from_arc(inner); + Bound::new(py, (child, parent)) + } +} + +impl<'py, M> IntoPyObject<'py> for EndpointWrapper> +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, + OpChainBuilder: InternalPropertyFilterBuilderOps, +{ + type Target = PyFilterOps; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let inner: Arc>> = Arc::new(self); + PyFilterOps::from_arc(inner).into_pyobject(py) + } +} + +impl<'py> IntoPyObject<'py> for PyPropertyFilterBuilder { + type Target = PyPropertyFilterBuilder; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let parent = PyFilterOps::from_arc(self.ops.clone()); + Bound::new(py, (self, parent)) + } +} + +pub trait DynPropertyFilterFactory: Send + Sync + 'static { + fn property(&self, name: String) -> PyPropertyFilterBuilder; + + fn metadata(&self, name: String) -> PyFilterOps; +} + +impl DynPropertyFilterFactory for T { + fn property(&self, name: String) -> PyPropertyFilterBuilder { + PyPropertyFilterBuilder::from_arc(Arc::new(self.property(name))) + } + + fn metadata(&self, name: String) -> PyFilterOps { + PyFilterOps::wrap(self.metadata(name)) + } +} + +#[pyclass( + name = "PropertyFilterFactory", + module = "raphtory.filter", + subclass, + frozen +)] +pub struct PyPropertyFilterFactory(Arc); + +impl PyPropertyFilterFactory { + pub fn wrap(value: T) -> Self { + Self(Arc::new(value)) + } +} + +#[pymethods] +impl PyPropertyFilterFactory { + fn property(&self, name: String) -> PyPropertyFilterBuilder { + self.0.property(name) + } + + fn metadata(&self, name: String) -> PyFilterOps { + self.0.metadata(name) + } +} diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs deleted file mode 100644 index a99ff17468..0000000000 --- a/raphtory/src/python/filter/window_filter.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{ - db::graph::views::filter::model::{ - edge_filter::{EdgeFilter, ExplodedEdgeFilter}, - node_filter::NodeFilter, - property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - Windowed, - }, - python::filter::property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, -}; -use pyo3::prelude::*; - -#[pyclass(frozen, name = "NodeWindow", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyNodeWindow(pub Windowed); - -#[pymethods] -impl PyNodeWindow { - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } -} - -#[pyclass(frozen, name = "EdgeWindow", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyEdgeWindow(pub Windowed); - -#[pymethods] -impl PyEdgeWindow { - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } -} - -#[pyclass(frozen, name = "ExplodedEdgeWindow", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyExplodedEdgeWindow(pub Windowed); - -#[pymethods] -impl PyExplodedEdgeWindow { - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); - b.into_pyobject(py) - } -} diff --git a/raphtory/src/python/graph/edges.rs b/raphtory/src/python/graph/edges.rs index 048a78ba53..43a30379f4 100644 --- a/raphtory/src/python/graph/edges.rs +++ b/raphtory/src/python/graph/edges.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{DynamicGraph, IntoDynBoxed, IntoDynamic, IterFilterOps, StaticGraphViewOps}, + api::view::{DynamicGraph, IntoDynBoxed, IntoDynamic, Select, StaticGraphViewOps}, graph::{ edge::EdgeView, edges::{Edges, NestedEdges}, @@ -406,7 +406,7 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NestedEdges fn into_pyobject(self, py: Python<'py>) -> Result { let edges = NestedEdges { nodes: self.nodes, - base_graph: self.base_graph.into_dynamic(), + graph: self.graph.into_dynamic(), edges: self.edges, }; PyNestedEdges { edges }.into_pyobject(py) @@ -421,7 +421,7 @@ impl<'graph, G: GraphViewOps<'graph>> Repr for NestedEdges<'graph, G> { impl From> for PyNestedEdges { fn from(value: NestedEdges<'static, G>) -> Self { - let base_graph = value.base_graph.into_dynamic(); + let base_graph = value.graph.into_dynamic(); Self { edges: NestedEdges::new(base_graph, value.nodes, value.edges), } diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 1a84f47465..a5ccefd3e6 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -6,7 +6,14 @@ use crate::{ db::{ api::{ properties::{Metadata, Properties}, - state::{ops, LazyNodeState, NodeStateOps}, + state::{ + ops, + ops::{ + filter::{AndOp, NodeTypeFilterOp, NO_FILTER}, + Degree, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, + }, + LazyNodeState, NodeStateOps, + }, view::{ internal::{ DynOrMutableGraph, DynamicGraph, IntoDynHop, IntoDynamic, IntoDynamicOrMutable, @@ -26,7 +33,7 @@ use crate::{ python::{ filter::filter_expr::PyFilterExpr, graph::{ - node::internal::BaseFilter, + node::internal::InternalFilter, properties::{MetadataView, PropertiesView, PyMetadataListList, PyNestedPropsIterable}, }, types::{iterable::FromIterable, repr::StructReprBuilder, wrappers::iterables::*}, @@ -37,7 +44,11 @@ use crate::{ use chrono::{DateTime, Utc}; use numpy::{IntoPyArray, Ix1, PyArray}; use pyo3::{ - exceptions::PyKeyError, prelude::*, pybacked::PyBackedStr, pyclass, pymethods, types::PyDict, + exceptions::{PyKeyError, PyTypeError}, + prelude::*, + pybacked::PyBackedStr, + pyclass, pymethods, + types::PyDict, IntoPyObjectExt, PyObject, PyResult, Python, }; use python::{ @@ -440,19 +451,35 @@ impl PyMutableNode { #[derive(Clone)] #[pyclass(name = "Nodes", module = "raphtory", frozen)] pub struct PyNodes { - pub(crate) nodes: Nodes<'static, DynamicGraph, DynamicGraph>, + pub(crate) nodes: Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>, } -impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph> { +impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { Ok(ob.downcast::()?.get().nodes.clone()) } } +impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph> { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let nodes = &ob.downcast::()?.get().nodes; + if nodes.selector.is_filtered() { + Err(PyTypeError::new_err("Expected unfiltered nodes")) + } else { + Ok(Nodes::new_filtered( + nodes.base_graph.clone(), + nodes.graph.clone(), + NO_FILTER, + nodes.nodes.clone(), + )) + } + } +} + impl_nodeviewops!( PyNodes, nodes, - Nodes<'static, DynamicGraph, DynamicGraph>, + Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>, "Nodes", "NestedEdges", "PathFromGraph" @@ -486,20 +513,28 @@ impl PyNodes { } } -impl - From> for PyNodes +impl< + G: StaticGraphViewOps + IntoDynamic, + GH: StaticGraphViewOps + IntoDynamic, + F: IntoDynNodeOp, + > From> for PyNodes { - fn from(value: Nodes<'static, G, GH>) -> Self { - let graph = value.iter_graph.into_dynamic(); + fn from(value: Nodes<'static, G, GH, F>) -> Self { let base_graph = value.base_graph.into_dynamic(); + let graph = value.graph.into_dynamic(); + let select = value.selector.into_dynamic(); Self { - nodes: Nodes::new_filtered(base_graph, graph, value.nodes, value.node_types_filter), + nodes: Nodes::new_filtered(base_graph, graph, select, value.nodes), } } } -impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: StaticGraphViewOps + IntoDynamic> - IntoPyObject<'py> for Nodes<'static, G, GH> +impl< + 'py, + G: StaticGraphViewOps + IntoDynamic, + GH: StaticGraphViewOps + IntoDynamic, + F: IntoDynNodeOp, + > IntoPyObject<'py> for Nodes<'static, G, GH, F> { type Target = PyNodes; type Output = Bound<'py, Self::Target>; @@ -550,7 +585,7 @@ impl PyNodes { /// Returns: /// IdView: a view of the node ids #[getter] - fn id(&self) -> LazyNodeState<'static, ops::Id, DynamicGraph, DynamicGraph> { + fn id(&self) -> LazyNodeState<'static, ops::Id, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.id() } @@ -559,7 +594,7 @@ impl PyNodes { /// Returns: /// NameView: a view of the node names #[getter] - fn name(&self) -> LazyNodeState<'static, ops::Name, DynamicGraph, DynamicGraph> { + fn name(&self) -> LazyNodeState<'static, ops::Name, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.name() } @@ -570,7 +605,13 @@ impl PyNodes { #[getter] fn earliest_time( &self, - ) -> LazyNodeState<'static, ops::EarliestTime, DynamicGraph, DynamicGraph> { + ) -> LazyNodeState< + 'static, + ops::EarliestTime, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.earliest_time() } @@ -585,6 +626,8 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynamicGraph, + DynNodeFilter, > { self.nodes.earliest_date_time() } @@ -594,7 +637,15 @@ impl PyNodes { /// Returns: /// LatestTimeView: a view of the latest active times #[getter] - fn latest_time(&self) -> LazyNodeState<'static, ops::LatestTime, DynamicGraph> { + fn latest_time( + &self, + ) -> LazyNodeState< + 'static, + ops::LatestTime, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.latest_time() } @@ -609,6 +660,8 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynamicGraph, + DynNodeFilter, > { self.nodes.latest_date_time() } @@ -618,7 +671,10 @@ impl PyNodes { /// Returns: /// HistoryView: a view of the node histories /// - fn history(&self) -> LazyNodeState<'static, ops::History, DynamicGraph> { + fn history( + &self, + ) -> LazyNodeState<'static, ops::History, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.history() } @@ -628,7 +684,13 @@ impl PyNodes { /// EdgeHistoryCountView: a view of the edge history counts fn edge_history_count( &self, - ) -> LazyNodeState<'static, ops::EdgeHistoryCount, DynamicGraph> { + ) -> LazyNodeState< + 'static, + ops::EdgeHistoryCount, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.edge_history_count() } @@ -637,7 +699,9 @@ impl PyNodes { /// Returns: /// NodeTypeView: a view of the node types #[getter] - fn node_type(&self) -> LazyNodeState<'static, ops::Type, DynamicGraph> { + fn node_type( + &self, + ) -> LazyNodeState<'static, ops::Type, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.node_type() } @@ -652,6 +716,8 @@ impl PyNodes { 'static, ops::Map, Option>>>, DynamicGraph, + DynamicGraph, + DynNodeFilter, > { self.nodes.history_date_time() } @@ -680,7 +746,10 @@ impl PyNodes { /// /// Returns: /// DegreeView: a view of the undirected node degrees - fn degree(&self) -> LazyNodeState<'static, ops::Degree, DynamicGraph> { + fn degree( + &self, + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.degree() } @@ -688,7 +757,10 @@ impl PyNodes { /// /// Returns: /// DegreeView: a view of the in-degrees of the nodes - fn in_degree(&self) -> LazyNodeState<'static, ops::Degree, DynamicGraph> { + fn in_degree( + &self, + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.in_degree() } @@ -696,7 +768,10 @@ impl PyNodes { /// /// Returns: /// DegreeView: a view of the out-degrees of the nodes - fn out_degree(&self) -> LazyNodeState<'static, ops::Degree, DynamicGraph> { + fn out_degree( + &self, + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.out_degree() } @@ -780,12 +855,17 @@ impl PyNodes { /// /// Returns: /// Nodes: the filtered view of the nodes - pub fn type_filter(&self, node_types: Vec) -> Nodes<'static, DynamicGraph> { + pub fn type_filter( + &self, + node_types: Vec, + ) -> Nodes<'static, DynamicGraph, DynamicGraph, AndOp> { self.nodes.type_filter(&node_types) } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> Repr for Nodes<'static, G, GH> { +impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, F: NodeFilterOp + 'graph> Repr + for Nodes<'static, G, GH, F> +{ fn repr(&self) -> String { format!("Nodes({})", iterator_repr(self.iter())) } diff --git a/raphtory/src/python/graph/node_state/node_state.rs b/raphtory/src/python/graph/node_state/node_state.rs index d1abfeab4b..75dbf44c6e 100644 --- a/raphtory/src/python/graph/node_state/node_state.rs +++ b/raphtory/src/python/graph/node_state/node_state.rs @@ -4,16 +4,22 @@ use crate::{ db::{ api::{ state::{ - ops, LazyNodeState, NodeGroups, NodeOp, NodeState, NodeStateGroupBy, NodeStateOps, + ops, + ops::{DynNodeFilter, IntoDynNodeOp}, + LazyNodeState, NodeGroups, NodeOp, NodeState, NodeStateGroupBy, NodeStateOps, OrderedNodeStateOps, }, view::{DynamicGraph, GraphViewOps}, }, - graph::{node::NodeView, nodes::Nodes}, + graph::{ + node::NodeView, + nodes::{IntoDynNodes, Nodes}, + }, }, prelude::*, py_borrowing_iter, python::{ + graph::node_state::node_state::ops::NodeFilterOp, types::{repr::Repr, wrappers::iterators::PyBorrowingIterator}, utils::PyNodeRef, }, @@ -46,8 +52,8 @@ macro_rules! impl_node_state_ops { /// /// Returns: /// Nodes: The nodes - fn nodes(&self) -> Nodes<'static, DynamicGraph> { - self.inner.nodes() + fn nodes(&self) -> Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter> { + self.inner.nodes().into_dyn() } fn __eq__<'py>( @@ -317,11 +323,13 @@ macro_rules! impl_lazy_node_state { /// A lazy view over node values #[pyclass(module = "raphtory.node_state", frozen)] pub struct $name { - inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph>, + inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter>, } impl $name { - pub fn inner(&self) -> &LazyNodeState<'static, $op, DynamicGraph, DynamicGraph> { + pub fn inner( + &self, + ) -> &LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter> { &self.inner } } @@ -332,9 +340,7 @@ macro_rules! impl_lazy_node_state { /// /// Returns: #[doc = concat!(" ", $computed, ": the computed `NodeState`")] - fn compute( - &self, - ) -> NodeState<'static, <$op as NodeOp>::Output, DynamicGraph, DynamicGraph> { + fn compute(&self) -> NodeState<'static, <$op as NodeOp>::Output, DynamicGraph> { self.inner.compute() } @@ -350,20 +356,24 @@ macro_rules! impl_lazy_node_state { impl_node_state_ops!( $name, <$op as NodeOp>::Output, - LazyNodeState<'static, $op, DynamicGraph, DynamicGraph>, + LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter>, |v: <$op as NodeOp>::Output| v, $computed, $py_value ); - impl From> for $name { - fn from(inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph>) -> Self { - $name { inner } + impl + From> for $name + { + fn from(inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, F>) -> Self { + $name { + inner: inner.into_dyn(), + } } } - impl<'py> pyo3::IntoPyObject<'py> - for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph> + impl<'py, F: IntoDynNodeOp + NodeFilterOp + 'static> pyo3::IntoPyObject<'py> + for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, F> { type Target = $name; type Output = Bound<'py, Self::Target>; @@ -374,7 +384,9 @@ macro_rules! impl_lazy_node_state { } } - impl<'py> FromPyObject<'py> for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph> { + impl<'py> FromPyObject<'py> + for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter> + { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { Ok(ob.downcast::<$name>()?.get().inner().clone()) } @@ -386,11 +398,11 @@ macro_rules! impl_node_state { ($name:ident<$value:ty>, $computed:literal, $py_value:literal) => { #[pyclass(module = "raphtory.node_state", frozen)] pub struct $name { - inner: NodeState<'static, $value, DynamicGraph, DynamicGraph>, + inner: NodeState<'static, $value, DynamicGraph>, } impl $name { - pub fn inner(&self) -> &NodeState<'static, $value, DynamicGraph, DynamicGraph> { + pub fn inner(&self) -> &NodeState<'static, $value, DynamicGraph> { &self.inner } } @@ -398,21 +410,19 @@ macro_rules! impl_node_state { impl_node_state_ops!( $name, $value, - NodeState<'static, $value, DynamicGraph, DynamicGraph>, + NodeState<'static, $value, DynamicGraph>, |v: &$value| v.clone(), $computed, $py_value ); - impl From> for $name { - fn from(inner: NodeState<'static, $value, DynamicGraph, DynamicGraph>) -> Self { + impl From> for $name { + fn from(inner: NodeState<'static, $value, DynamicGraph>) -> Self { $name { inner: inner } } } - impl<'py> pyo3::IntoPyObject<'py> - for NodeState<'static, $value, DynamicGraph, DynamicGraph> - { + impl<'py> pyo3::IntoPyObject<'py> for NodeState<'static, $value, DynamicGraph> { type Target = $name; type Output = Bound<'py, Self::Target>; type Error = >::Error; @@ -422,7 +432,7 @@ macro_rules! impl_node_state { } } - impl<'py> FromPyObject<'py> for NodeState<'static, $value, DynamicGraph, DynamicGraph> { + impl<'py> FromPyObject<'py> for NodeState<'static, $value, DynamicGraph> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { Ok(ob.downcast::<$name>()?.get().inner().clone()) } diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index d5d1f9f09e..25ae2dfa8a 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -3,9 +3,12 @@ use crate::{ db::{ api::{ properties::{Metadata, Properties}, + state::ops::filter::NodeTypeFilterOp, view::{ - filter_ops::BaseFilterOps, - internal::{BaseFilter, DynamicGraph, IntoDynHop, IntoDynamic, MaterializedGraph}, + filter_ops::Filter, + internal::{ + DynamicGraph, InternalFilter, IntoDynHop, IntoDynamic, MaterializedGraph, + }, LayerOps, StaticGraphViewOps, }, }, @@ -20,8 +23,7 @@ use crate::{ filter::{ edge_property_filtered_graph::EdgePropertyFilteredGraph, exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, - node_property_filtered_graph::NodePropertyFilteredGraph, - node_type_filtered_graph::NodeTypeFilteredGraph, + node_filtered_graph::NodeFilteredGraph, }, layer_graph::LayeredGraph, node_subgraph::NodeSubgraph, @@ -135,16 +137,6 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for CachedView< } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodeTypeFilteredGraph { - type Target = PyGraphView; - type Output = >::Output; - type Error = >::Error; - - fn into_pyobject(self, py: Python<'py>) -> Result { - PyGraphView::from(self).into_pyobject(py) - } -} - impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for EdgePropertyFilteredGraph { type Target = PyGraphView; type Output = >::Output; @@ -155,16 +147,6 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for EdgePropert } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropertyFilteredGraph { - type Target = PyGraphView; - type Output = >::Output; - type Error = >::Error; - - fn into_pyobject(self, py: Python<'py>) -> Result { - PyGraphView::from(self).into_pyobject(py) - } -} - impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for ExplodedEdgePropertyFilteredGraph { @@ -441,8 +423,10 @@ impl PyGraphView { /// /// Returns: /// GraphView: Returns the subgraph - fn subgraph_node_types(&self, node_types: Vec) -> NodeTypeFilteredGraph { - self.graph.subgraph_node_types(node_types) + fn subgraph_node_types(&self, node_types: Vec) -> PyGraphView { + let subgraph = self.graph.subgraph_node_types(node_types); + let dyn_graph = subgraph.into_dyn_hop(); + PyGraphView::from(dyn_graph) } /// Returns a subgraph given a set of nodes that are excluded from the subgraph diff --git a/raphtory/src/python/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs index 96d8a72333..e3c6285b51 100644 --- a/raphtory/src/python/packages/algorithms.rs +++ b/raphtory/src/python/packages/algorithms.rs @@ -59,7 +59,7 @@ use crate::{ }, db::{ api::{ - state::{Index, NodeState}, + state::{ops::filter::NO_FILTER, Index, NodeState}, view::internal::DynamicGraph, }, graph::{node::NodeView, nodes::Nodes}, @@ -777,7 +777,7 @@ pub fn k_core( } else { Some(Index::from_iter(v_set)) }; - Nodes::new_filtered(graph.graph.clone(), graph.graph.clone(), index, None) + Nodes::new_filtered(graph.graph.clone(), graph.graph.clone(), NO_FILTER, index) } /// Simulate an SEIR dynamic on the network diff --git a/raphtory/src/python/types/macros/trait_impl/filter_ops.rs b/raphtory/src/python/types/macros/trait_impl/filter_ops.rs index bbc522c12b..0255e3f202 100644 --- a/raphtory/src/python/types/macros/trait_impl/filter_ops.rs +++ b/raphtory/src/python/types/macros/trait_impl/filter_ops.rs @@ -1,3 +1,6 @@ +use crate::db::api::view::internal::InternalFilter; +use pyo3::prelude::PyResult; + /// Macro for implementing all the FilterOps methods on a python wrapper /// /// # Arguments @@ -5,6 +8,7 @@ /// * field: The name of the struct field holding the rust struct implementing `FilterOps` /// * base_type: The rust type of `field` /// * name: The name of the object that appears in the docstring + macro_rules! impl_filter_ops { ($obj:ident<$base_type:ty>, $field:ident, $name:literal) => { #[pyo3::pymethods] @@ -19,8 +23,7 @@ macro_rules! impl_filter_ops { fn filter( &self, filter: PyFilterExpr, - ) -> Result<<$base_type as BaseFilter<'static>>::Filtered, GraphError> - { + ) -> PyResult<<$base_type as InternalFilter<'static>>::Filtered> { Ok(self.$field.clone().filter(filter)?.into_dyn_hop()) } } diff --git a/raphtory/src/python/types/repr.rs b/raphtory/src/python/types/repr.rs index ab4ca1fe47..661909891e 100644 --- a/raphtory/src/python/types/repr.rs +++ b/raphtory/src/python/types/repr.rs @@ -1,6 +1,6 @@ use crate::{ core::storage::locked_view::LockedView, - db::api::state::{LazyNodeState, NodeOp, NodeState}, + db::api::state::{ops::NodeFilterOp, LazyNodeState, NodeOp, NodeState}, prelude::{GraphViewOps, NodeStateOps, NodeViewOps}, }; use bigdecimal::BigDecimal; @@ -272,8 +272,13 @@ impl Repr for &R { } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, Op: NodeOp + 'graph> Repr - for LazyNodeState<'graph, Op, G, GH> +impl< + 'graph, + G: GraphViewOps<'graph>, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + 'graph, + Op: NodeOp + 'graph, + > Repr for LazyNodeState<'graph, Op, G, GH, F> where Op::Output: Repr + Send + Sync + 'graph, { @@ -284,12 +289,8 @@ where } } -impl< - 'graph, - G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, - V: Repr + Clone + Send + Sync + 'graph, - > Repr for NodeState<'graph, V, G, GH> +impl<'graph, G: GraphViewOps<'graph>, V: Repr + Clone + Send + Sync + 'graph> Repr + for NodeState<'graph, V, G> { fn repr(&self) -> String { StructReprBuilder::new("NodeState") diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 1fb82d42c3..5dcbdf7108 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -1,24 +1,24 @@ use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{internal::FilterOps, BoxableGraphView, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeEdgeFilter, EdgeFieldFilter, EdgeFilter}, + edge_filter::{CompositeEdgeFilter, EdgeFilter}, property_filter::PropertyRef, - Filter, }, }, }, }, errors::GraphError, - prelude::{GraphViewOps, PropertyFilter, TimeOps}, + prelude::{GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, search::{ collectors::unique_entity_filter_collector::UniqueEntityFilterCollector, fallback_filter_edges, fields, get_reader, graph_index::Index, - property_index::PropertyIndex, query_builder::QueryBuilder, + node_filter_executor::NodeFilterExecutor, property_index::PropertyIndex, + query_builder::QueryBuilder, }, }; use itertools::Itertools; @@ -210,30 +210,6 @@ impl<'a> EdgeFilterExecutor<'a> { } } - fn filter_edge_index( - &self, - graph: &G, - filter: &Filter, - limit: usize, - offset: usize, - ) -> Result>, GraphError> { - let (edge_index, query) = self.query_builder.build_edge_query(filter)?; - let reader = get_reader(&edge_index.entity_index.index)?; - let results = match query { - Some(query) => self.execute_filter_query( - EdgeFieldFilter(filter.clone()), - graph, - query, - &reader, - limit, - offset, - )?, - None => fallback_filter_edges(graph, &EdgeFieldFilter(filter.clone()), limit, offset)?, - }; - - Ok(results) - } - pub fn filter_edges_internal( &self, graph: &G, @@ -242,31 +218,44 @@ impl<'a> EdgeFilterExecutor<'a> { offset: usize, ) -> Result>, GraphError> { match filter { + CompositeEdgeFilter::Src(node_filter) => { + let nfe = NodeFilterExecutor::new(self.index); + let nodes = nfe.filter_nodes(graph, node_filter, usize::MAX, 0)?; + let mut edges: Vec> = nodes + .into_iter() + .flat_map(|n| n.out_edges().into_iter()) + .collect(); + if offset != 0 || limit < edges.len() { + edges = edges.into_iter().skip(offset).take(limit).collect(); + } + Ok(edges) + } + CompositeEdgeFilter::Dst(node_filter) => { + let nfe = NodeFilterExecutor::new(self.index); + let nodes = nfe.filter_nodes(graph, node_filter, usize::MAX, 0)?; + let mut edges: Vec> = nodes + .into_iter() + .flat_map(|n| n.in_edges().into_iter()) + .collect(); + if offset != 0 || limit < edges.len() { + edges = edges.into_iter().skip(offset).take(limit).collect(); + } + Ok(edges) + } CompositeEdgeFilter::Property(filter) => { self.filter_property_index(graph, filter, limit, offset) } - CompositeEdgeFilter::PropertyWindowed(filter) => { - let start = filter.entity.start.t(); - let end = filter.entity.end.t(); - - let filter = PropertyFilter { - prop_ref: filter.prop_ref.clone(), - prop_value: filter.prop_value.clone(), - operator: filter.operator, - ops: filter.ops.clone(), - entity: EdgeFilter, - }; - - let res = - self.filter_property_index(&graph.window(start, end), &filter, limit, offset)?; + CompositeEdgeFilter::Windowed(filter) => { + let start = filter.start.t(); + let end = filter.end.t(); + let dyn_graph: Arc = Arc::new((*graph).clone()); + let dyn_graph = dyn_graph.window(start, end); + let res = self.filter_edges(&dyn_graph, &filter.inner, limit, offset)?; Ok(res .into_iter() .map(|x| EdgeView::new(graph.clone(), x.edge)) .collect()) } - CompositeEdgeFilter::Edge(filter) => { - self.filter_edge_index(graph, filter, limit, offset) - } CompositeEdgeFilter::And(left, right) => { let left_result = self.filter_edges(graph, left, limit, offset)?; let right_result = self.filter_edges(graph, right, limit, offset)?; diff --git a/raphtory/src/search/exploded_edge_filter_executor.rs b/raphtory/src/search/exploded_edge_filter_executor.rs index e1d6f1c044..44b6535f8b 100644 --- a/raphtory/src/search/exploded_edge_filter_executor.rs +++ b/raphtory/src/search/exploded_edge_filter_executor.rs @@ -1,12 +1,12 @@ use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{internal::FilterOps, BoxableGraphView, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeExplodedEdgeFilter, EdgeFilter, ExplodedEdgeFilter}, + exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, property_filter::PropertyRef, }, }, @@ -222,23 +222,20 @@ impl<'a> ExplodedEdgeFilterExecutor<'a> { offset: usize, ) -> Result>, GraphError> { match filter { + CompositeExplodedEdgeFilter::Src(_) | CompositeExplodedEdgeFilter::Dst(_) => { + // TODO: Can we use an index here to speed up search? + fallback_filter_exploded_edges(graph, filter, limit, offset) + } CompositeExplodedEdgeFilter::Property(filter) => { self.filter_property_index(graph, filter, limit, offset) } - CompositeExplodedEdgeFilter::PropertyWindowed(filter) => { - let start = filter.entity.start.t(); - let end = filter.entity.end.t(); - - let filter = PropertyFilter { - prop_ref: filter.prop_ref.clone(), - prop_value: filter.prop_value.clone(), - operator: filter.operator, - ops: filter.ops.clone(), - entity: ExplodedEdgeFilter, - }; + CompositeExplodedEdgeFilter::Windowed(filter) => { + let start = filter.start.t(); + let end = filter.end.t(); - let res = - self.filter_property_index(&graph.window(start, end), &filter, limit, offset)?; + let dyn_graph: Arc = Arc::new((*graph).clone()); + let dyn_graph = dyn_graph.window(start, end); + let res = self.filter_exploded_edges(&dyn_graph, &filter.inner, limit, offset)?; Ok(res .into_iter() .map(|x| EdgeView::new(graph.clone(), x.edge)) diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs index 96f0a3832a..52ee5da40d 100644 --- a/raphtory/src/search/mod.rs +++ b/raphtory/src/search/mod.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{filter_ops::BaseFilterOps, StaticGraphViewOps}, + api::view::{filter_ops::Filter, StaticGraphViewOps}, graph::{edge::EdgeView, node::NodeView, views::filter::internal::CreateFilter}, }, errors::GraphError, @@ -127,15 +127,14 @@ pub(crate) fn get_reader(index: &Arc) -> Result pub(crate) fn fallback_filter_nodes( graph: &G, - filter: &(impl CreateFilter + Clone), + filter: &(impl CreateFilter + Clone + 'static), limit: usize, offset: usize, ) -> Result>, GraphError> { let filtered_nodes = graph - .filter(filter.clone())? .nodes() - .iter() - .map(|n| NodeView::new_internal(graph.clone(), n.node)) + .select(filter.clone())? + .into_iter() .skip(offset) .take(limit) .collect(); diff --git a/raphtory/src/search/node_filter_executor.rs b/raphtory/src/search/node_filter_executor.rs index 75c50342c7..1086b194aa 100644 --- a/raphtory/src/search/node_filter_executor.rs +++ b/raphtory/src/search/node_filter_executor.rs @@ -1,8 +1,7 @@ use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{BoxableGraphView, StaticGraphViewOps}, graph::{ - edge::EdgeView, node::NodeView, views::filter::{ internal::CreateFilter, @@ -250,20 +249,12 @@ impl<'a> NodeFilterExecutor<'a> { CompositeNodeFilter::Property(filter) => { self.filter_property_index(graph, filter, limit, offset) } - CompositeNodeFilter::PropertyWindowed(filter) => { - let start = filter.entity.start.t(); - let end = filter.entity.end.t(); - - let filter = PropertyFilter { - prop_ref: filter.prop_ref.clone(), - prop_value: filter.prop_value.clone(), - operator: filter.operator, - ops: filter.ops.clone(), - entity: NodeFilter, - }; - - let res = - self.filter_property_index(&graph.window(start, end), &filter, limit, offset)?; + CompositeNodeFilter::Windowed(filter) => { + let start = filter.start.t(); + let end = filter.end.t(); + let dyn_graph: Arc = Arc::new((*graph).clone()); + let dyn_graph = dyn_graph.window(start, end); + let res = self.filter_nodes(&dyn_graph, &filter.inner, limit, offset)?; Ok(res .into_iter() .map(|x| NodeView::new_internal(graph.clone(), x.node)) @@ -341,14 +332,14 @@ impl<'a> NodeFilterExecutor<'a> { graph: &G, node_ids: HashSet, ) -> Result>, GraphError> { - let filtered_graph = graph.filter(filter)?; + let nodes = graph.nodes().select(filter)?; let nodes = node_ids .into_iter() .filter_map(|id| { - let n_ref = graph.core_node(VID(id as usize)); - filtered_graph - .filter_node(n_ref.as_ref()) - .then(|| NodeView::new_internal(graph.clone(), VID(id as usize))) + let vid = VID(id as usize); + nodes + .contains(vid) + .then(|| NodeView::new_internal(graph.clone(), vid)) }) .collect_vec(); Ok(nodes) diff --git a/raphtory/src/search/node_index.rs b/raphtory/src/search/node_index.rs index b54051b60f..848a27d849 100644 --- a/raphtory/src/search/node_index.rs +++ b/raphtory/src/search/node_index.rs @@ -216,6 +216,10 @@ impl NodeIndex { let node_type = node.node_type(); let node_doc = self.create_document(node_id, node_name.clone(), node_type.clone()); + println!( + "Indexing Node Document: {}", + node_doc.to_json(&self.entity_index.index.schema()) // assumes `self.index` has `schema() -> &Schema` + ); writer.add_document(node_doc)?; Ok(()) diff --git a/raphtory/src/search/searcher.rs b/raphtory/src/search/searcher.rs index 727bde789a..4344debf17 100644 --- a/raphtory/src/search/searcher.rs +++ b/raphtory/src/search/searcher.rs @@ -182,12 +182,12 @@ mod search_tests { db::{ api::view::SearchableGraphOps, graph::views::filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, - PropertyFilterFactory, TryAsCompositeFilter, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, PropertyFilterFactory, + TryAsCompositeFilter, }, }, - prelude::{AdditionOps, EdgeViewOps, Graph, IndexMutationOps, NodeViewOps}, + prelude::{AdditionOps, EdgeViewOps, Graph, IndexMutationOps, NodeViewOps, NO_PROPS}, }; use raphtory_api::core::entities::properties::prop::IntoProp; @@ -221,6 +221,10 @@ mod search_tests { ) .unwrap(); + graph.add_node(1, "shivam", NO_PROPS, None).unwrap(); + graph.add_node(1, "raphtory", NO_PROPS, None).unwrap(); + graph.add_node(1, "pometry", NO_PROPS, None).unwrap(); + graph.create_index_in_ram().unwrap(); let mut results = graph