From 06dbc2e93750602d1a2415b35f535111902a3e64 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:03:00 +0100 Subject: [PATCH 01/42] redesign, refactor property filters --- python/python/raphtory/__init__.pyi | 56 - .../python/raphtory/algorithms/__init__.pyi | 3 - python/python/raphtory/filter/__init__.pyi | 32 +- .../test_edge_property_filter_semantics.py | 8 +- .../test_node_property_filter_semantics.py | 8 +- .../test_filters/test_edge_property_filter.py | 46 +- .../test_filters/test_exploded_edge_filter.py | 20 +- .../test_filters/test_node_property_filter.py | 58 +- .../test_graphdb/test_persistent_graph.py | 2 +- .../test_graph_nodes_property_filter.py | 2 +- .../test_nodes_property_filter.py | 43 +- python/tests/test_base_install/test_index.py | 84 +- .../entities/properties/prop/prop_type.rs | 7 + raphtory-graphql/schema.graphql | 67 +- raphtory-graphql/src/model/graph/filtering.rs | 147 +- raphtory/src/db/graph/views/filter/mod.rs | 278 +-- .../views/filter/model/property_filter.rs | 1676 +++++++++-------- raphtory/src/python/filter/mod.rs | 2 - .../python/filter/property_filter_builders.rs | 271 +-- raphtory/src/search/edge_filter_executor.rs | 4 +- .../search/exploded_edge_filter_executor.rs | 4 +- raphtory/src/search/mod.rs | 6 +- raphtory/src/search/node_filter_executor.rs | 4 +- 23 files changed, 1381 insertions(+), 1447 deletions(-) diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi index 0a17666de6..e7d3659c82 100644 --- a/python/python/raphtory/__init__.pyi +++ b/python/python/raphtory/__init__.pyi @@ -49,7 +49,6 @@ __all__ = [ "IndexSpec", "Prop", "version", - "DiskGraphStorage", "graphql", "algorithms", "graph_loader", @@ -1376,17 +1375,6 @@ class Graph(GraphView): MutableNode: The node object with the specified id, or None if the node does not exist """ - def persist_as_disk_graph(self, graph_dir: str | PathLike) -> DiskGraphStorage: - """ - save graph in disk_graph format and memory map the result - - Arguments: - graph_dir (str | PathLike): folder where the graph will be saved - - Returns: - DiskGraphStorage: the persisted graph storage - """ - def persistent_graph(self) -> PersistentGraph: """ View graph with persistent semantics @@ -1424,17 +1412,6 @@ class Graph(GraphView): bytes: """ - def to_disk_graph(self, graph_dir: str | PathLike) -> Graph: - """ - Persist graph on disk - - Arguments: - graph_dir (str | PathLike): the folder where the graph will be persisted - - Returns: - Graph: a view of the persisted graph - """ - def to_parquet(self, graph_dir: str | PathLike): """ Persist graph to parquet files @@ -2209,7 +2186,6 @@ class PersistentGraph(GraphView): bytes: """ - def to_disk_graph(self, graph_dir): ... def update_metadata(self, metadata: dict) -> None: """ Updates metadata of the graph. @@ -6142,35 +6118,3 @@ class Prop(object): def u8(value): ... def version(): ... - -class DiskGraphStorage(object): - def __repr__(self): - """Return repr(self).""" - - def append_node_temporal_properties(self, location, chunk_size=20000000): ... - def graph_dir(self): ... - @staticmethod - def load_from_dir(graph_dir): ... - @staticmethod - def load_from_pandas(graph_dir, edge_df, time_col, src_col, dst_col): ... - @staticmethod - def load_from_parquets( - graph_dir, - layer_parquet_cols, - node_properties=None, - chunk_size=10000000, - t_props_chunk_size=10000000, - num_threads=4, - node_type_col=None, - node_id_col=None, - ): ... - def load_node_metadata(self, location, col_names=None, chunk_size=None): ... - def load_node_types(self, location, col_name, chunk_size=None): ... - def merge_by_sorted_gids(self, other, graph_dir): - """ - Merge this graph with another `DiskGraph`. Note that both graphs should have nodes that are - sorted by their global ids or the resulting graph will be nonsense! - """ - - def to_events(self): ... - def to_persistent(self): ... diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi index 02ea5eba3e..3873e9b001 100644 --- a/python/python/raphtory/algorithms/__init__.pyi +++ b/python/python/raphtory/algorithms/__init__.pyi @@ -70,7 +70,6 @@ __all__ = [ "max_weight_matching", "Matching", "Infected", - "connected_components", ] def dijkstra_single_source_shortest_paths( @@ -894,5 +893,3 @@ class Infected(object): Returns: int: """ - -def connected_components(graph): ... diff --git a/python/python/raphtory/filter/__init__.pyi b/python/python/raphtory/filter/__init__.pyi index 2c89c6da12..94427efba7 100644 --- a/python/python/raphtory/filter/__init__.pyi +++ b/python/python/raphtory/filter/__init__.pyi @@ -32,7 +32,6 @@ __all__ = [ "ExplodedEdge", "Property", "Metadata", - "TemporalPropertyFilterBuilder", ] class FilterExpr(object): @@ -75,11 +74,13 @@ class PropertyFilterOps(object): 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 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): ... @@ -184,32 +185,3 @@ class Metadata(PropertyFilterOps): Arguments: name (str): the name of the property to filter """ - -class TemporalPropertyFilterBuilder(object): - def __eq__(self, value): - """Return self==value.""" - - def __ge__(self, value): - """Return self>=value.""" - - def __gt__(self, value): - """Return self>value.""" - - def __le__(self, value): - """Return self<=value.""" - - def __lt__(self, value): - """Return self 1, 4), - (filter.ExplodedEdge.property("weight").temporal().latest() <= 2, 4), - (filter.ExplodedEdge.property("weight").temporal().latest() >= 3, 2), - (filter.ExplodedEdge.property("weight").temporal().latest().is_in([1, 2]), 4), - (filter.ExplodedEdge.property("weight").temporal().latest().is_not_in([3]), 4), - (filter.ExplodedEdge.property("weight").temporal().latest().is_some(), 6), - (filter.ExplodedEdge.property("weight").temporal().latest().is_none(), 0), + (filter.ExplodedEdge.property("weight").temporal().last() == 2, 2), + (filter.ExplodedEdge.property("weight").temporal().last() != 3, 4), + (filter.ExplodedEdge.property("weight").temporal().last() < 3, 4), + (filter.ExplodedEdge.property("weight").temporal().last() > 1, 4), + (filter.ExplodedEdge.property("weight").temporal().last() <= 2, 4), + (filter.ExplodedEdge.property("weight").temporal().last() >= 3, 2), + (filter.ExplodedEdge.property("weight").temporal().last().is_in([1, 2]), 4), + (filter.ExplodedEdge.property("weight").temporal().last().is_not_in([3]), 4), + (filter.ExplodedEdge.property("weight").temporal().last().is_some(), 6), + (filter.ExplodedEdge.property("weight").temporal().last().is_none(), 0), (filter.ExplodedEdge.property("p20").temporal().first().starts_with("Old"), 1), (filter.ExplodedEdge.property("p20").temporal().first().ends_with("boat"), 1), ] diff --git a/python/tests/test_base_install/test_filters/test_node_property_filter.py b/python/tests/test_base_install/test_filters/test_node_property_filter.py index 8b2d0fe5b4..8164e4a309 100644 --- a/python/tests/test_base_install/test_filters/test_node_property_filter.py +++ b/python/tests/test_base_install/test_filters/test_node_property_filter.py @@ -155,7 +155,7 @@ def check(graph): expected_ids = [] assert result_ids == expected_ids - filter_expr = filter.Node.property("p10").temporal().latest().starts_with("P") + filter_expr = filter.Node.property("p10").temporal().last().starts_with("P") result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["1", "2", "3"] assert result_ids == expected_ids @@ -198,7 +198,7 @@ def check(graph): expected_ids = ["1", "3"] assert result_ids == expected_ids - filter_expr = filter.Node.property("p10").temporal().latest().ends_with("ship") + filter_expr = filter.Node.property("p10").temporal().last().ends_with("ship") result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["2"] assert result_ids == expected_ids @@ -236,7 +236,7 @@ def check(graph): expected_ids = ["1", "2", "3"] assert result_ids == expected_ids - filter_expr = filter.Node.property("p10").temporal().latest().contains("Paper") + filter_expr = filter.Node.property("p10").temporal().last().contains("Paper") result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["1", "2", "3"] assert result_ids == expected_ids @@ -269,9 +269,7 @@ def check(graph): expected_ids = ["1", "3"] assert result_ids == expected_ids - filter_expr = ( - filter.Node.property("p10").temporal().latest().not_contains("ship") - ) + filter_expr = filter.Node.property("p10").temporal().last().not_contains("ship") result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["1", "3"] assert result_ids == expected_ids @@ -361,9 +359,9 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporal_latest_property_sum(): +def test_filter_nodes_for_temporal_last_property_sum(): def check(graph): - filter_expr = filter.Node.property("prop6").temporal().latest().sum() == 12 + filter_expr = filter.Node.property("prop6").temporal().last().sum() == 12 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a"] assert result_ids == expected_ids @@ -372,9 +370,9 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporal_latest_property_avg(): +def test_filter_nodes_for_temporal_last_property_avg(): def check(graph): - filter_expr = filter.Node.property("prop6").temporal().latest().avg() == 4.0 + filter_expr = filter.Node.property("prop6").temporal().last().avg() == 4.0 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a"] assert result_ids == expected_ids @@ -383,9 +381,9 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporal_latest_property_min(): +def test_filter_nodes_for_temporal_last_property_min(): def check(graph): - filter_expr = filter.Node.property("prop6").temporal().latest().min() == 3 + filter_expr = filter.Node.property("prop6").temporal().last().min() == 3 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a"] assert result_ids == expected_ids @@ -394,9 +392,9 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporal_latest_property_max(): +def test_filter_nodes_for_temporal_last_property_max(): def check(graph): - filter_expr = filter.Node.property("prop6").temporal().latest().max() == 5 + filter_expr = filter.Node.property("prop6").temporal().last().max() == 5 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a"] assert result_ids == expected_ids @@ -405,11 +403,11 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporal_latest_property_len(): +def test_filter_nodes_for_temporal_last_property_len(): def check(graph): - filter_expr = filter.Node.property( - "prop6" - ).temporal().latest().len() == Prop.u64(3) + filter_expr = filter.Node.property("prop6").temporal().last().len() == Prop.u64( + 3 + ) result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a"] assert result_ids == expected_ids @@ -728,9 +726,9 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporary_property_latest_any(): +def test_filter_nodes_for_temporary_property_last_any(): def check(graph): - filter_expr = filter.Node.property("prop8").temporal().latest().any() == 3 + filter_expr = filter.Node.property("prop8").temporal().last().any() == 3 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a", "d"] assert result_ids == expected_ids @@ -739,14 +737,14 @@ def check(graph): @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) -def test_filter_nodes_for_temporary_property_latest_all(): +def test_filter_nodes_for_temporary_property_last_all(): def check(graph): - filter_expr = filter.Node.property("prop8").temporal().latest().all() > 1 + filter_expr = filter.Node.property("prop8").temporal().last().all() > 1 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["a", "d"] assert result_ids == expected_ids - filter_expr = filter.Node.property("prop8").temporal().latest().all() > 2 + filter_expr = filter.Node.property("prop8").temporal().last().all() > 2 result_ids = sorted(graph.filter(filter_expr).nodes.id) expected_ids = ["d"] assert result_ids == expected_ids @@ -1074,7 +1072,7 @@ def test_filter_nodes_for_temporal_property_ne(): def check(graph): filter_expr = filter.Node.property("prop1").temporal() != [60] result_ids = sorted(graph.filter(filter_expr).nodes.id) - expected_ids = ['b', 'c', 'd'] + expected_ids = ["b", "c", "d"] assert result_ids == expected_ids return check @@ -1084,18 +1082,18 @@ def check(graph): def test_filter_nodes_for_temporal_property_fails(): def check(graph): filter_expr = filter.Node.property("prop1").temporal() == 60 - msg = "Invalid filter: temporal() == / != expects a list value (e.g. [1,2,3])" + msg = "Wrong type for property prop1: expected List(I64) but actual type is I64" with pytest.raises( - Exception, - match=re.escape(msg), + Exception, + match=re.escape(msg), ): graph.filter(filter_expr).nodes.id filter_expr = filter.Node.property("prop1").temporal() == "pometry" - msg = "Invalid filter: temporal() == / != expects a list value (e.g. [1,2,3])" + msg = "Wrong type for property prop1: expected List(I64) but actual type is Str" with pytest.raises( - Exception, - match=re.escape(msg), + Exception, + match=re.escape(msg), ): graph.filter(filter_expr).nodes.id diff --git a/python/tests/test_base_install/test_graphdb/test_persistent_graph.py b/python/tests/test_base_install/test_graphdb/test_persistent_graph.py index 3b7369ce32..c80a54c631 100644 --- a/python/tests/test_base_install/test_graphdb/test_persistent_graph.py +++ b/python/tests/test_base_install/test_graphdb/test_persistent_graph.py @@ -256,7 +256,7 @@ def test_filtering_valid(): g.add_edge(2, 1, 3, layer="blue", properties={"weight": 2}) g.add_edge(3, 1, 3, layer="red", properties={"weight": 3}) - f = filter.Edge.property("weight").temporal().latest() < 3 + f = filter.Edge.property("weight").temporal().last() < 3 e = g.filter(f).edge(1, 2) assert e is None # assert list(e.properties.temporal.get("weight").values) == [1,2] -- this would be the case for filter_edge_layer? diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py index 6a5332d040..f6b238df81 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py @@ -953,7 +953,7 @@ def test_graph_nodes_property_filter_starts_with(graph): filter: { temporalProperty: { name: "prop3", - temporal: ALL, + ops: [ALL], operator: STARTS_WITH value: { str: "abc1" } } diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index c77589cca6..e2859e4ccd 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -904,7 +904,7 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): filter: { temporalProperty: { name: "prop3", - temporal: FIRST, + ops: [FIRST] operator: STARTS_WITH value: { str: "abc" } } @@ -940,7 +940,7 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): filter: { temporalProperty: { name: "prop3", - temporal: ALL, + ops: [ALL] operator: STARTS_WITH value: { str: "abc1" } } @@ -967,8 +967,8 @@ def test_nodes_property_filter_list_agg(graph): property: { name: "prop5" operator: EQUAL + ops: [SUM] value: { i64: 6 } - listAgg:SUM } }) { nodes { @@ -993,8 +993,8 @@ def test_nodes_property_filter_list_qualifier(graph): property: { name: "prop5" operator: EQUAL + ops: [ANY] value: { i64: 6 } - elemQualifier: ANY } }) { nodes { @@ -1022,9 +1022,8 @@ def test_nodes_temporal_property_filter_agg(graph): nodeFilter(filter: { temporalProperty: { name: "p2" - temporal:VALUES operator: LESS_THAN - listAgg: AVG + ops: [AVG] value: { f64: 10.0 } } }) { @@ -1041,3 +1040,35 @@ def test_nodes_temporal_property_filter_agg(graph): "graph": {"nodeFilter": {"nodes": {"list": [{"name": "2"}, {"name": "3"}]}}} } run_graphql_test(query, expected_output, graph) + + +EVENT_GRAPH = create_test_graph(Graph()) +PERSISTENT_GRAPH = create_test_graph(PersistentGraph()) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_temporal_property_filter_any_avg(graph): + query = """ + query { + graph(path: "g") { + nodeFilter(filter: { + temporalProperty: { + name: "prop5" + operator: LESS_THAN + ops: [ANY, AVG] + value: { f64: 10.0 } + } + }) { + nodes { + list { + name + } + } + } + } + } + """ + expected_output = { + "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} + } + run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_index.py b/python/tests/test_base_install/test_index.py index a6780de3a0..92c44eda7d 100644 --- a/python/tests/test_base_install/test_index.py +++ b/python/tests/test_base_install/test_index.py @@ -534,38 +534,38 @@ def test_search_nodes_for_property_temporal_any_is_none(): assert [] == results -def test_search_nodes_for_property_temporal_latest_eq(): +def test_search_nodes_for_property_temporal_last_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() == 1 + filter_expr = filter.Node.property("p1").temporal().last() == 1 results = search_nodes(g, filter_expr) assert ["N1", "N3", "N4", "N6", "N7"] == results -def test_search_nodes_for_property_temporal_latest_ne(): +def test_search_nodes_for_property_temporal_last_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() != 2 + filter_expr = filter.Node.property("p1").temporal().last() != 2 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N3", "N4", "N6", "N7"] == results -def test_search_nodes_for_property_temporal_latest_lt(): +def test_search_nodes_for_property_temporal_last_lt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() < 2 + filter_expr = filter.Node.property("p1").temporal().last() < 2 results = search_nodes(g, filter_expr) assert ["N1", "N3", "N4", "N6", "N7"] == results -def test_search_nodes_for_property_temporal_latest_le(): +def test_search_nodes_for_property_temporal_last_le(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() <= 3 + filter_expr = filter.Node.property("p1").temporal().last() <= 3 results = search_nodes(g, filter_expr) assert [ "N1", @@ -584,47 +584,47 @@ def test_search_nodes_for_property_temporal_latest_le(): ] == results -def test_search_nodes_for_property_temporal_latest_gt(): +def test_search_nodes_for_property_temporal_last_gt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() > 1 + filter_expr = filter.Node.property("p1").temporal().last() > 1 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_latest_ge(): +def test_search_nodes_for_property_temporal_last_ge(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() >= 2 + filter_expr = filter.Node.property("p1").temporal().last() >= 2 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_latest_is_in(): +def test_search_nodes_for_property_temporal_last_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_in([2]) + filter_expr = filter.Node.property("p1").temporal().last().is_in([2]) results = search_nodes(g, filter_expr) assert ["N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_latest_is_not_in(): +def test_search_nodes_for_property_temporal_last_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_not_in([2]) + filter_expr = filter.Node.property("p1").temporal().last().is_not_in([2]) results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N3", "N4", "N6", "N7"] == results -def test_search_nodes_for_property_temporal_latest_is_some(): +def test_search_nodes_for_property_temporal_last_is_some(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_some() + filter_expr = filter.Node.property("p1").temporal().last().is_some() results = search_nodes(g, filter_expr) assert [ "N1", @@ -654,11 +654,11 @@ def test_search_nodes_for_composite_filter(): @pytest.mark.skip(reason="Ignoring this test temporarily") -def test_search_nodes_for_property_temporal_latest_is_none(): +def test_search_nodes_for_property_temporal_last_is_none(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_none() + filter_expr = filter.Node.property("p1").temporal().last().is_none() results = search_nodes(g, filter_expr) assert [] == results @@ -1343,11 +1343,11 @@ def test_search_edges_for_property_temporal_any_is_none(): assert [] == results -def test_search_edges_for_property_temporal_latest_eq(): +def test_search_edges_for_property_temporal_last_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() == 1 + filter_expr = filter.Edge.property("p1").temporal().last() == 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1358,11 +1358,11 @@ def test_search_edges_for_property_temporal_latest_eq(): ] == results -def test_search_edges_for_property_temporal_latest_ne(): +def test_search_edges_for_property_temporal_last_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() != 2 + filter_expr = filter.Edge.property("p1").temporal().last() != 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1377,11 +1377,11 @@ def test_search_edges_for_property_temporal_latest_ne(): ] == results -def test_search_edges_for_property_temporal_latest_lt(): +def test_search_edges_for_property_temporal_last_lt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() < 2 + filter_expr = filter.Edge.property("p1").temporal().last() < 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1392,11 +1392,11 @@ def test_search_edges_for_property_temporal_latest_lt(): ] == results -def test_search_edges_for_property_temporal_latest_le(): +def test_search_edges_for_property_temporal_last_le(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() <= 3 + filter_expr = filter.Edge.property("p1").temporal().last() <= 3 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1415,11 +1415,11 @@ def test_search_edges_for_property_temporal_latest_le(): ] == results -def test_search_edges_for_property_temporal_latest_gt(): +def test_search_edges_for_property_temporal_last_gt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() > 1 + filter_expr = filter.Edge.property("p1").temporal().last() > 1 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -1433,11 +1433,11 @@ def test_search_edges_for_property_temporal_latest_gt(): ] == results -def test_search_edges_for_property_temporal_latest_ge(): +def test_search_edges_for_property_temporal_last_ge(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() >= 2 + filter_expr = filter.Edge.property("p1").temporal().last() >= 2 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -1451,20 +1451,20 @@ def test_search_edges_for_property_temporal_latest_ge(): ] == results -def test_search_edges_for_property_temporal_latest_is_in(): +def test_search_edges_for_property_temporal_last_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_in([2]) + filter_expr = filter.Edge.property("p1").temporal().last().is_in([2]) results = search_edges(g, filter_expr) assert [("N2", "N3"), ("N5", "N6"), ("N8", "N9"), ("N9", "N10")] == results -def test_search_edges_for_property_temporal_latest_is_not_in(): +def test_search_edges_for_property_temporal_last_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_not_in([2]) + filter_expr = filter.Edge.property("p1").temporal().last().is_not_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1479,11 +1479,11 @@ def test_search_edges_for_property_temporal_latest_is_not_in(): ] == results -def test_search_edges_for_property_temporal_latest_is_some(): +def test_search_edges_for_property_temporal_last_is_some(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_some() + filter_expr = filter.Edge.property("p1").temporal().last().is_some() results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1503,11 +1503,11 @@ def test_search_edges_for_property_temporal_latest_is_some(): @pytest.mark.skip(reason="Ignoring this test temporarily") -def test_search_edges_for_property_temporal_latest_is_none(): +def test_search_edges_for_property_temporal_last_is_none(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_none() + filter_expr = filter.Edge.property("p1").temporal().last().is_none() results = search_edges(g, filter_expr) assert [] == results @@ -1517,7 +1517,7 @@ def test_search_edges_for_composite_filter(): g = init_edges_graph(g) filter1 = filter.Edge.src().name() == "N13" - filter2 = filter.Edge.property("p1").temporal().latest() == 3 + filter2 = filter.Edge.property("p1").temporal().last() == 3 results = search_edges(g, filter1 & filter2) assert [("N13", "N14")] == results @@ -1527,6 +1527,6 @@ def test_search_edges_for_composite_filter_pg(): g = init_edges_graph(g) filter1 = filter.Edge.src().name() == "N13" - filter2 = filter.Edge.property("p1").temporal().latest() == 3 + filter2 = filter.Edge.property("p1").temporal().last() == 3 results = search_edges(g, filter1 & filter2) assert [("N13", "N14")] == results diff --git a/raphtory-api/src/core/entities/properties/prop/prop_type.rs b/raphtory-api/src/core/entities/properties/prop/prop_type.rs index 8bd80958ee..cb29ceffdb 100644 --- a/raphtory-api/src/core/entities/properties/prop/prop_type.rs +++ b/raphtory-api/src/core/entities/properties/prop/prop_type.rs @@ -78,6 +78,13 @@ impl Display for PropType { } impl PropType { + pub fn inner(&self) -> Option<&PropType> { + match self { + PropType::List(inner) => Some(inner.as_ref()), + _ => None, + } + } + pub fn map(fields: impl IntoIterator, PropType)>) -> Self { let map: HashMap<_, _> = fields.into_iter().map(|(k, v)| (k.into(), v)).collect(); PropType::Map(Arc::from(map)) diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index 2867c0ef03..f95736a85f 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -1053,19 +1053,6 @@ type LayerSchema { edges: [EdgeSchema!]! } -enum ListAgg { - LEN - SUM - AVG - MIN - MAX -} - -enum ListElemQualifier { - ANY - ALL -} - type MetaGraph { """ Returns the graph name. @@ -1134,13 +1121,9 @@ input MetadataFilterExpr { """ value: Value """ - List aggregate - """ - listAgg: ListAgg - """ - List qualifier + Ops List. """ - elemQualifier: ListElemQualifier + ops: [OpName!] } type MutRoot { @@ -1891,6 +1874,18 @@ input ObjectEntry { value: Value! } +enum OpName { + FIRST + LAST + ANY + ALL + LEN + SUM + AVG + MIN + MAX +} + enum Operator { """ Equality operator. @@ -2161,13 +2156,9 @@ input PropertyFilterExpr { """ value: Value """ - List aggregate - """ - listAgg: ListAgg - """ - List qualifier + Ops List. """ - elemQualifier: ListElemQualifier + ops: [OpName!] } input PropertyInput { @@ -2331,10 +2322,6 @@ input TemporalPropertyFilterExpr { """ name: String! """ - Type of temporal property. Choose from: any, latest. - """ - temporal: TemporalType! - """ Operator. """ operator: Operator! @@ -2343,13 +2330,9 @@ input TemporalPropertyFilterExpr { """ value: Value """ - List aggregate - """ - listAgg: ListAgg + Ops List. """ - List qualifier - """ - elemQualifier: ListElemQualifier + ops: [OpName!] } input TemporalPropertyInput { @@ -2363,20 +2346,6 @@ input TemporalPropertyInput { properties: [PropertyInput!] } -enum TemporalType { - """ - Any. - """ - ANY - """ - Latest. - """ - LATEST - FIRST - ALL - VALUES -} - scalar Upload input Value @oneOf { diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 7ca96e2051..6e859f7e82 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -11,9 +11,7 @@ use raphtory::{ edge_filter::CompositeEdgeFilter, filter_operator::FilterOperator, node_filter::CompositeNodeFilter, - property_filter::{ - ListAgg, ListElemQualifier, PropertyFilter, PropertyFilterValue, PropertyRef, Temporal, - }, + property_filter::{Op, PropertyFilter, PropertyFilterValue, PropertyRef}, Filter, FilterValue, }, errors::GraphError, @@ -426,23 +424,23 @@ pub enum GqlListElemQualifier { All, } -impl From for ListElemQualifier { +impl From for Op { fn from(q: GqlListElemQualifier) -> Self { match q { - GqlListElemQualifier::Any => ListElemQualifier::Any, - GqlListElemQualifier::All => ListElemQualifier::All, + GqlListElemQualifier::Any => Op::Any, + GqlListElemQualifier::All => Op::All, } } } -impl From for ListAgg { +impl From for Op { fn from(a: GqlListAgg) -> Self { match a { - GqlListAgg::Len => ListAgg::Len, - GqlListAgg::Sum => ListAgg::Sum, - GqlListAgg::Avg => ListAgg::Avg, - GqlListAgg::Min => ListAgg::Min, - GqlListAgg::Max => ListAgg::Max, + GqlListAgg::Len => Op::Len, + GqlListAgg::Sum => Op::Sum, + GqlListAgg::Avg => Op::Avg, + GqlListAgg::Min => Op::Min, + GqlListAgg::Max => Op::Max, } } } @@ -455,21 +453,13 @@ pub struct PropertyFilterExpr { pub operator: Operator, /// Value. pub value: Option, - /// List aggregate - pub list_agg: Option, - /// List qualifier - pub elem_qualifier: Option, + /// Ops List. + pub ops: Option>, } impl PropertyFilterExpr { pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref())?; - if self.elem_qualifier.is_some() && self.list_agg.is_some() { - return Err(GraphError::InvalidGqlFilter( - "List aggregation and element qualifier cannot be used together".into(), - )); - } - Ok(()) + validate_operator_value_pair(self.operator, self.value.as_ref()) } } @@ -481,21 +471,13 @@ pub struct MetadataFilterExpr { pub operator: Operator, /// Value. pub value: Option, - /// List aggregate - pub list_agg: Option, - /// List qualifier - pub elem_qualifier: Option, + /// Ops List. + pub ops: Option>, } impl MetadataFilterExpr { pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref())?; - if self.elem_qualifier.is_some() && self.list_agg.is_some() { - return Err(GraphError::InvalidGqlFilter( - "List aggregation and element qualifier cannot be used together".into(), - )); - } - Ok(()) + validate_operator_value_pair(self.operator, self.value.as_ref()) } } @@ -503,49 +485,47 @@ impl MetadataFilterExpr { pub struct TemporalPropertyFilterExpr { /// Name. pub name: String, - /// Type of temporal property. Choose from: any, latest. - pub temporal: TemporalType, /// Operator. pub operator: Operator, /// Value. pub value: Option, - /// List aggregate - pub list_agg: Option, - /// List qualifier - pub elem_qualifier: Option, + /// Ops List. + pub ops: Option>, } impl TemporalPropertyFilterExpr { pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref())?; - - if let TemporalType::Values = self.temporal { - if self.list_agg.is_none() { - return Err(GraphError::InvalidGqlFilter( - "temporal: VALUES must be used with a list_agg (len/sum/avg/min/max)".into(), - )); - } - if self.elem_qualifier.is_some() { - return Err(GraphError::InvalidGqlFilter( - "Element qualifiers (any/all) are not supported with temporal VALUES aggregation" - .into(), - )); - } - } - - Ok(()) + validate_operator_value_pair(self.operator, self.value.as_ref()) } } #[derive(Enum, Copy, Clone, Debug)] -pub enum TemporalType { - /// Any. - Any, - /// Latest. - Latest, +pub enum OpName { First, + Last, + Any, All, - Values, + Len, + Sum, + Avg, + Min, + Max, +} + +impl From for Op { + fn from(o: OpName) -> Self { + match o { + OpName::First => Op::First, + OpName::Last => Op::Last, + OpName::Any => Op::Any, + OpName::All => Op::All, + OpName::Len => Op::Len, + OpName::Sum => Op::Sum, + OpName::Avg => Op::Avg, + OpName::Min => Op::Min, + OpName::Max => Op::Max, + } + } } fn field_value(value: Value, operator: Operator) -> Result { @@ -838,8 +818,7 @@ fn build_property_filter( prop_ref: PropertyRef, operator: Operator, value: Option<&Value>, - list_agg: Option, - list_elem_qualifier: Option, + ops: Vec, ) -> Result, GraphError> { let prop = value.cloned().map(Prop::try_from).transpose()?; @@ -857,8 +836,7 @@ fn build_property_filter( prop_ref, prop_value, operator: operator.into(), - list_agg: list_agg.map(Into::into), - list_elem_qualifier: list_elem_qualifier.map(Into::into), + ops, _phantom: PhantomData, }) } @@ -867,12 +845,15 @@ impl TryFrom for PropertyFilter { type Error = GraphError; fn try_from(expr: PropertyFilterExpr) -> Result { + expr.validate()?; + + let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); + build_property_filter( PropertyRef::Property(expr.name), expr.operator, expr.value.as_ref(), - expr.list_agg, - expr.elem_qualifier, + ops, ) } } @@ -881,12 +862,15 @@ impl TryFrom for PropertyFilter { type Error = GraphError; fn try_from(expr: MetadataFilterExpr) -> Result { + expr.validate()?; + + let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); + build_property_filter( PropertyRef::Metadata(expr.name), expr.operator, expr.value.as_ref(), - expr.list_agg, - expr.elem_qualifier, + ops, ) } } @@ -895,12 +879,15 @@ impl TryFrom for PropertyFilter { type Error = GraphError; fn try_from(expr: TemporalPropertyFilterExpr) -> Result { + expr.validate()?; + + let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); + build_property_filter( - PropertyRef::TemporalProperty(expr.name, expr.temporal.into()), + PropertyRef::TemporalProperty(expr.name), expr.operator, expr.value.as_ref(), - expr.list_agg, - expr.elem_qualifier, + ops, ) } } @@ -937,18 +924,6 @@ impl From for FilterOperator { } } -impl From for Temporal { - fn from(temporal: TemporalType) -> Self { - match temporal { - TemporalType::Any => Temporal::Any, - TemporalType::Latest => Temporal::Latest, - TemporalType::First => Temporal::First, - TemporalType::All => Temporal::All, - TemporalType::Values => Temporal::Values, - } - } -} - fn validate_id_operator_value_pair(operator: Operator, value: &Value) -> Result<(), GraphError> { use Operator::*; diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 2b500e03d2..67df94252e 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -22,7 +22,7 @@ mod test_fluent_builder_apis { use crate::db::graph::views::filter::model::{ edge_filter::{CompositeEdgeFilter, EdgeFilter, EdgeFilterOps}, node_filter::{CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps}, - property_filter::{PropertyFilter, PropertyFilterOps, PropertyRef, Temporal}, + property_filter::{ElemQualifierOps, Op, PropertyFilter, PropertyFilterOps, PropertyRef}, ComposableFilter, Filter, PropertyFilterFactory, TryAsCompositeFilter, }; @@ -52,21 +52,21 @@ mod test_fluent_builder_apis { fn test_node_any_temporal_property_filter_build() { let filter_expr = NodeFilter::property("p").temporal().any().eq("raphtory"); let node_property_filter = filter_expr.try_as_composite_node_filter().unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p".to_string(), Temporal::Any), - "raphtory", - )); + let node_property_filter2 = CompositeNodeFilter::Property( + PropertyFilter::eq(PropertyRef::TemporalProperty("p".to_string()), "raphtory") + .with_op(Op::Any), + ); assert_eq!(node_property_filter, node_property_filter2); } #[test] fn test_node_latest_temporal_property_filter_build() { - let filter_expr = NodeFilter::property("p").temporal().latest().eq("raphtory"); + let filter_expr = NodeFilter::property("p").temporal().last().eq("raphtory"); let node_property_filter = filter_expr.try_as_composite_node_filter().unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p".to_string(), Temporal::Latest), - "raphtory", - )); + let node_property_filter2 = CompositeNodeFilter::Property( + PropertyFilter::eq(PropertyRef::TemporalProperty("p".to_string()), "raphtory") + .with_op(Op::Last), + ); assert_eq!(node_property_filter, node_property_filter2); } @@ -97,7 +97,7 @@ mod test_fluent_builder_apis { .temporal() .any() .eq(5u64) - .or(NodeFilter::property("p4").temporal().latest().eq(7u64)), + .or(NodeFilter::property("p4").temporal().last().eq(7u64)), ) .or(NodeFilter::node_type().eq("raphtory")) .or(NodeFilter::property("p5").eq(9u64)) @@ -124,14 +124,20 @@ mod test_fluent_builder_apis { ))), )), Box::new(CompositeNodeFilter::Or( - Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p3".to_string(), Temporal::Any), - 5u64, - ))), - Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p4".to_string(), Temporal::Latest), - 7u64, - ))), + Box::new(CompositeNodeFilter::Property( + PropertyFilter::eq( + PropertyRef::TemporalProperty("p3".to_string()), + 5u64, + ) + .with_op(Op::Any), + )), + Box::new(CompositeNodeFilter::Property( + PropertyFilter::eq( + PropertyRef::TemporalProperty("p4".to_string()), + 7u64, + ) + .with_op(Op::Last), + )), )), )), Box::new(CompositeNodeFilter::Node(Filter::eq( @@ -176,7 +182,7 @@ mod test_fluent_builder_apis { .temporal() .any() .eq(5u64) - .or(EdgeFilter::property("p4").temporal().latest().eq(7u64)), + .or(EdgeFilter::property("p4").temporal().last().eq(7u64)), ) .or(EdgeFilter::src().name().eq("raphtory")) .or(EdgeFilter::property("p5").eq(9u64)) @@ -200,14 +206,14 @@ mod test_fluent_builder_apis { ))), )), Box::new(CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p3".into(), Temporal::Any), - 5u64, - ))), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p4".into(), Temporal::Latest), - 7u64, - ))), + Box::new(CompositeEdgeFilter::Property( + PropertyFilter::eq(PropertyRef::TemporalProperty("p3".into()), 5u64) + .with_op(Op::Any), + )), + Box::new(CompositeEdgeFilter::Property( + PropertyFilter::eq(PropertyRef::TemporalProperty("p4".into()), 7u64) + .with_op(Op::Last), + )), )), )), Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory"))), @@ -650,7 +656,7 @@ pub(crate) mod test_filters { #[test] fn test_temporal_latest_semantics() { - let filter = NodeFilter::property("p1").temporal().latest().eq(1u64); + let filter = NodeFilter::property("p1").temporal().last().eq(1u64); let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; assert_filter_nodes_results( init_graph, @@ -670,7 +676,7 @@ pub(crate) mod test_filters { #[test] fn test_temporal_latest_semantics_for_secondary_indexes() { - let filter = NodeFilter::property("p1").temporal().latest().eq(1u64); + let filter = NodeFilter::property("p1").temporal().last().eq(1u64); let expected_results = vec!["N1", "N16", "N3", "N4", "N6", "N7"]; assert_filter_nodes_results( init_graph_for_secondary_indexes, @@ -1188,7 +1194,7 @@ pub(crate) mod test_filters { #[test] fn test_temporal_latest_semantics() { // TODO: PropertyFilteringNotImplemented for variants persistent_graph, persistent_disk_graph for filter_edges. - let filter = EdgeFilter::property("p1").temporal().latest().eq(1u64); + let filter = EdgeFilter::property("p1").temporal().last().eq(1u64); let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; assert_filter_edges_results( init_graph, @@ -1209,7 +1215,7 @@ pub(crate) mod test_filters { #[test] fn test_temporal_latest_semantics_for_secondary_indexes() { // TODO: PropertyFilteringNotImplemented for variants persistent_graph, persistent_disk_graph for filter_edges. - let filter = EdgeFilter::property("p1").temporal().latest().eq(1u64); + let filter = EdgeFilter::property("p1").temporal().last().eq(1u64); let expected_results = vec!["N1->N2", "N16->N15", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; assert_filter_edges_results( @@ -3262,7 +3268,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .starts_with("Pape"); let expected_results: Vec<&str> = vec!["1", "2", "3"]; assert_filter_nodes_results( @@ -3282,7 +3288,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .starts_with("Yohan"); let expected_results: Vec<&str> = vec![]; assert_filter_nodes_results( @@ -3382,7 +3388,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .ends_with("ane"); let expected_results: Vec<&str> = vec!["1", "3"]; assert_filter_nodes_results( @@ -3402,7 +3408,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .ends_with("Jerry"); let expected_results: Vec<&str> = vec![]; assert_filter_nodes_results( @@ -3502,7 +3508,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .contains("Paper"); let expected_results: Vec<&str> = vec!["1", "2", "3"]; assert_filter_nodes_results( @@ -3602,7 +3608,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p10") .temporal() - .latest() + .last() .not_contains("ship"); let expected_results: Vec<&str> = vec!["1", "3"]; assert_filter_nodes_results( @@ -5173,12 +5179,12 @@ pub(crate) mod test_filters { apply_assertion(filter, &expected); } - // ------ Temporal latest: SUM ------ + // ------ Temporal last: SUM ------ #[test] - fn test_node_property_temporal_latest_sum_u8s() { + fn test_node_property_temporal_last_sum_u8s() { let filter = NodeFilter::property("p_u8s") .temporal() - .latest() + .last() .sum() .eq(Prop::U64(10)); let expected = vec!["n1"]; @@ -5186,10 +5192,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_u16s() { + fn test_node_property_temporal_last_sum_u16s() { let filter = NodeFilter::property("p_u16s") .temporal() - .latest() + .last() .sum() .eq(Prop::U64(6)); let expected = vec!["n3", "n10"]; @@ -5197,10 +5203,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_u32s() { + fn test_node_property_temporal_last_sum_u32s() { let filter = NodeFilter::property("p_u32s") .temporal() - .latest() + .last() .sum() .eq(Prop::U64(10)); let expected = vec!["n1"]; @@ -5208,10 +5214,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_u64s() { + fn test_node_property_temporal_last_sum_u64s() { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .sum() .eq(Prop::U64(60)); let expected = vec!["n4"]; @@ -5219,10 +5225,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_i32s() { + fn test_node_property_temporal_last_sum_i32s() { let filter = NodeFilter::property("p_i32s") .temporal() - .latest() + .last() .sum() .eq(Prop::I64(60)); let expected = vec!["n4"]; @@ -5230,10 +5236,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_i64s() { + fn test_node_property_temporal_last_sum_i64s() { let filter = NodeFilter::property("p_i64s") .temporal() - .latest() + .last() .sum() .eq(Prop::I64(0)); let expected = vec!["n3", "n10"]; @@ -5241,10 +5247,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_f32s() { + fn test_node_property_temporal_last_sum_f32s() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .sum() .eq(Prop::F64(6.5)); let expected = vec!["n3", "n10"]; @@ -5252,22 +5258,22 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_sum_f64s() { + fn test_node_property_temporal_last_sum_f64s() { let filter = NodeFilter::property("p_f64s") .temporal() - .latest() + .last() .sum() .eq(Prop::F64(90.0)); let expected = vec!["n3", "n10"]; apply_assertion(filter, &expected); } - // ------ Temporal latest: AVG ------ + // ------ Temporal last: AVG ------ #[test] - fn test_node_property_temporal_latest_avg_u8s() { + fn test_node_property_temporal_last_avg_u8s() { let filter = NodeFilter::property("p_u8s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(2.5)); let expected = vec!["n1"]; @@ -5275,10 +5281,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_u16s() { + fn test_node_property_temporal_last_avg_u16s() { let filter = NodeFilter::property("p_u16s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(2.0)); let expected = vec!["n3", "n10"]; @@ -5286,10 +5292,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_u32s() { + fn test_node_property_temporal_last_avg_u32s() { let filter = NodeFilter::property("p_u32s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(2.5)); let expected = vec!["n1"]; @@ -5297,10 +5303,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_u64s() { + fn test_node_property_temporal_last_avg_u64s() { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(20.0)); let expected = vec!["n4"]; @@ -5308,10 +5314,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_i32s() { + fn test_node_property_temporal_last_avg_i32s() { let filter = NodeFilter::property("p_i32s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(0.6666666666666666)); let expected = vec!["n6"]; @@ -5319,10 +5325,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_i64s() { + fn test_node_property_temporal_last_avg_i64s() { let filter = NodeFilter::property("p_i64s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(0.0)); let expected = vec!["n3", "n10"]; @@ -5330,10 +5336,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_f32s() { + fn test_node_property_temporal_last_avg_f32s() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(20.0)); let expected = vec!["n4"]; @@ -5341,22 +5347,22 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_avg_f64s() { + fn test_node_property_temporal_last_avg_f64s() { let filter = NodeFilter::property("p_f64s") .temporal() - .latest() + .last() .avg() .eq(Prop::F64(45.0)); let expected = vec!["n3", "n10"]; apply_assertion(filter, &expected); } - // ------ Temporal latest: MIN ------ + // ------ Temporal last: MIN ------ #[test] - fn test_node_property_temporal_latest_min_u8s() { + fn test_node_property_temporal_last_min_u8s() { let filter = NodeFilter::property("p_u8s") .temporal() - .latest() + .last() .min() .eq(Prop::U8(1)); let expected = vec!["n1", "n3", "n10"]; @@ -5364,10 +5370,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_u16s() { + fn test_node_property_temporal_last_min_u16s() { let filter = NodeFilter::property("p_u16s") .temporal() - .latest() + .last() .min() .eq(Prop::U16(1)); let expected = vec!["n1", "n3", "n10"]; @@ -5375,10 +5381,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_u32s() { + fn test_node_property_temporal_last_min_u32s() { let filter = NodeFilter::property("p_u32s") .temporal() - .latest() + .last() .min() .eq(Prop::U32(1)); let expected = vec!["n1", "n3", "n10"]; @@ -5386,10 +5392,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_u64s() { + fn test_node_property_temporal_last_min_u64s() { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .min() .eq(Prop::U64(10)); let expected = vec!["n4"]; @@ -5397,10 +5403,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_i32s() { + fn test_node_property_temporal_last_min_i32s() { let filter = NodeFilter::property("p_i32s") .temporal() - .latest() + .last() .min() .eq(Prop::I32(-2)); let expected = vec!["n6"]; @@ -5408,10 +5414,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_i64s() { + fn test_node_property_temporal_last_min_i64s() { let filter = NodeFilter::property("p_i64s") .temporal() - .latest() + .last() .min() .eq(Prop::I64(-3)); let expected = vec!["n3", "n10"]; @@ -5419,10 +5425,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_f32s() { + fn test_node_property_temporal_last_min_f32s() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .min() .eq(Prop::F32(10.0)); let expected = vec!["n4"]; @@ -5430,22 +5436,22 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_min_f64s() { + fn test_node_property_temporal_last_min_f64s() { let filter = NodeFilter::property("p_f64s") .temporal() - .latest() + .last() .min() .eq(Prop::F64(40.0)); let expected = vec!["n3", "n10"]; apply_assertion(filter, &expected); } - // ------ Temporal latest: MAX ------ + // ------ Temporal last: MAX ------ #[test] - fn test_node_property_temporal_latest_max_u8s() { + fn test_node_property_temporal_last_max_u8s() { let filter = NodeFilter::property("p_u8s") .temporal() - .latest() + .last() .max() .eq(Prop::U8(4)); let expected = vec!["n1"]; @@ -5453,10 +5459,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_u16s() { + fn test_node_property_temporal_last_max_u16s() { let filter = NodeFilter::property("p_u16s") .temporal() - .latest() + .last() .max() .eq(Prop::U16(3)); let expected = vec!["n3", "n10"]; @@ -5464,10 +5470,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_u32s() { + fn test_node_property_temporal_last_max_u32s() { let filter = NodeFilter::property("p_u32s") .temporal() - .latest() + .last() .max() .eq(Prop::U32(4)); let expected = vec!["n1"]; @@ -5475,10 +5481,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_u64s() { + fn test_node_property_temporal_last_max_u64s() { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .max() .eq(Prop::U64(30)); let expected = vec!["n4"]; @@ -5486,10 +5492,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_i32s() { + fn test_node_property_temporal_last_max_i32s() { let filter = NodeFilter::property("p_i32s") .temporal() - .latest() + .last() .max() .eq(Prop::I32(3)); let expected = vec!["n3", "n6", "n10"]; @@ -5497,10 +5503,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_i64s() { + fn test_node_property_temporal_last_max_i64s() { let filter = NodeFilter::property("p_i64s") .temporal() - .latest() + .last() .max() .eq(Prop::I64(2)); let expected = vec!["n3", "n10"]; @@ -5508,10 +5514,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_f32s() { + fn test_node_property_temporal_last_max_f32s() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .max() .eq(Prop::F32(3.5)); let expected = vec!["n3", "n10"]; @@ -5519,22 +5525,22 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_max_f64s() { + fn test_node_property_temporal_last_max_f64s() { let filter = NodeFilter::property("p_f64s") .temporal() - .latest() + .last() .max() .eq(Prop::F64(50.0)); let expected = vec!["n1", "n2", "n3", "n10"]; apply_assertion(filter, &expected); } - // ------ Temporal latest: LEN ------ + // ------ Temporal last: LEN ------ #[test] - fn test_node_property_temporal_latest_len_u8s() { + fn test_node_property_temporal_last_len_u8s() { let filter = NodeFilter::property("p_u8s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n10"]; @@ -5542,10 +5548,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_u16s() { + fn test_node_property_temporal_last_len_u16s() { let filter = NodeFilter::property("p_u16s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n10"]; @@ -5553,10 +5559,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_u32s() { + fn test_node_property_temporal_last_len_u32s() { let filter = NodeFilter::property("p_u32s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n10"]; @@ -5564,10 +5570,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_u64s() { + fn test_node_property_temporal_last_len_u64s() { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n4", "n10"]; @@ -5575,10 +5581,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_i32s() { + fn test_node_property_temporal_last_len_i32s() { let filter = NodeFilter::property("p_i32s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n4", "n6", "n10"]; @@ -5586,10 +5592,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_i64s() { + fn test_node_property_temporal_last_len_i64s() { let filter = NodeFilter::property("p_i64s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n10"]; @@ -5597,10 +5603,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_f32s() { + fn test_node_property_temporal_last_len_f32s() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(3)); let expected = vec!["n3", "n4", "n10"]; @@ -5608,10 +5614,10 @@ pub(crate) mod test_filters { } #[test] - fn test_node_property_temporal_latest_len_f64s() { + fn test_node_property_temporal_last_len_f64s() { let filter = NodeFilter::property("p_f64s") .temporal() - .latest() + .last() .len() .eq(Prop::U64(2)); let expected = vec!["n3", "n10"]; @@ -7246,7 +7252,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p_u64s") .temporal() - .latest() + .last() .min() .eq(Prop::U64(0)); let expected: Vec<&str> = vec![]; @@ -7407,24 +7413,24 @@ pub(crate) mod test_filters { apply_assertion(filter, &expected); } - // ------ Temporal Latest: any ------ + // ------ Temporal last: any ------ #[test] - fn test_node_temporal_property_latest_any() { + fn test_node_temporal_property_last_any() { let filter = NodeFilter::property("p_f32s") .temporal() - .latest() + .last() .any() .eq(Prop::F32(3.5)); let expected = vec!["n1", "n10", "n3"]; apply_assertion(filter, &expected); } - // ------ Temporal Latest: all ------ + // ------ Temporal last: all ------ #[test] - fn test_node_temporal_property_latest_all() { + fn test_node_temporal_property_last_all() { let filter = NodeFilter::property("p_bools_all") .temporal() - .latest() + .last() .all() .eq(true); let expected = vec!["n10", "n4"]; @@ -9280,7 +9286,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .starts_with("Paper"); let expected_results: Vec<&str> = vec!["1->2", "2->1", "2->3"]; assert_filter_edges_results( @@ -9300,7 +9306,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .starts_with("Traffic"); let expected_results: Vec<&str> = vec![]; assert_filter_edges_results( @@ -9401,7 +9407,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .ends_with("ane"); let expected_results: Vec<&str> = vec!["1->2", "2->1"]; assert_filter_edges_results( @@ -9421,7 +9427,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .ends_with("marcus"); let expected_results: Vec<&str> = vec![]; assert_filter_edges_results( @@ -9522,7 +9528,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .contains("Paper"); let expected_results: Vec<&str> = vec!["1->2", "2->1", "2->3"]; assert_filter_edges_results( @@ -9623,7 +9629,7 @@ pub(crate) mod test_filters { let filter = EdgeFilter::property("p10") .temporal() - .latest() + .last() .not_contains("ship"); let expected_results: Vec<&str> = vec!["1->2", "2->1"]; assert_filter_edges_results( 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 4ce8dec912..b142c36847 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -39,42 +39,49 @@ use raphtory_storage::graph::{ use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Deref, sync::Arc}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ListAgg { +pub enum Op { + // Selectors + First, + Last, + // Aggregators Len, Sum, Avg, Min, Max, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ListElemQualifier { + // Qualifiers Any, All, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Temporal { - Any, - Latest, - First, - All, - Values, +impl Op { + #[inline] + pub fn is_selector(self) -> bool { + matches!(self, Op::First | Op::Last) + } + + #[inline] + pub fn is_aggregator(self) -> bool { + matches!(self, Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max) + } + + #[inline] + pub fn is_qualifier(self) -> bool { + matches!(self, Op::Any | Op::All) + } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PropertyRef { Property(String), Metadata(String), - TemporalProperty(String, Temporal), + TemporalProperty(String), } impl Display for PropertyRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PropertyRef::TemporalProperty(name, temporal) => { - write!(f, "TemporalProperty({}, {:?})", name, temporal) - } + PropertyRef::TemporalProperty(name) => write!(f, "TemporalProperty({})", name), PropertyRef::Metadata(name) => write!(f, "Metadata({})", name), PropertyRef::Property(name) => write!(f, "Property({})", name), } @@ -86,7 +93,7 @@ impl PropertyRef { match self { PropertyRef::Property(name) | PropertyRef::Metadata(name) - | PropertyRef::TemporalProperty(name, _) => name, + | PropertyRef::TemporalProperty(name) => name, } } } @@ -103,58 +110,59 @@ pub struct PropertyFilter { pub prop_ref: PropertyRef, pub prop_value: PropertyFilterValue, pub operator: FilterOperator, - pub list_agg: Option, - pub list_elem_qualifier: Option, + pub ops: Vec, // at most 2 (validated) pub _phantom: PhantomData, } impl Display for PropertyFilter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let base = match &self.prop_ref { + let mut expr = match &self.prop_ref { PropertyRef::Property(name) => name.to_string(), PropertyRef::Metadata(name) => format!("const({})", name), - PropertyRef::TemporalProperty(name, t) => { - format!("temporal_{:?}({})", t, name) - } - }; - - let qualified = match self.list_elem_qualifier { - Some(ListElemQualifier::Any) => format!("any({})", base), - Some(ListElemQualifier::All) => format!("all({})", base), - None => base, + PropertyRef::TemporalProperty(name) => format!("temporal({})", name), }; - let decorated = match self.list_agg { - Some(ListAgg::Len) => format!("len({})", qualified), - Some(ListAgg::Sum) => format!("sum({})", qualified), - Some(ListAgg::Avg) => format!("avg({})", qualified), - Some(ListAgg::Min) => format!("min({})", qualified), - Some(ListAgg::Max) => format!("max({})", qualified), - None => qualified, - }; + for op in &self.ops { + expr = match op { + Op::First => format!("first({})", expr), + Op::Last => format!("last({})", expr), + Op::Len => format!("len({})", expr), + Op::Sum => format!("sum({})", expr), + Op::Avg => format!("avg({})", expr), + Op::Min => format!("min({})", expr), + Op::Max => format!("max({})", expr), + Op::Any => format!("any({})", expr), + Op::All => format!("all({})", expr), + }; + } match &self.prop_value { - PropertyFilterValue::None => write!(f, "{} {}", decorated, self.operator), - PropertyFilterValue::Single(value) => { - write!(f, "{} {} {}", decorated, self.operator, value) - } + PropertyFilterValue::None => write!(f, "{} {}", expr, self.operator), + PropertyFilterValue::Single(value) => write!(f, "{} {} {}", expr, self.operator, value), PropertyFilterValue::Set(values) => { let sorted = sort_comparable_props(values.iter().collect_vec()); let values_str = sorted.iter().map(|v| format!("{}", v)).join(", "); - write!(f, "{} {} [{}]", decorated, self.operator, values_str) + write!(f, "{} {} [{}]", expr, self.operator, values_str) } } } } +enum ValueType { + Seq(Vec), + Scalar(Option), +} + impl PropertyFilter { - pub fn with_list_agg(mut self, agg: Option) -> Self { - self.list_agg = agg; + #[inline] + pub fn with_op(mut self, op: Op) -> Self { + self.ops.push(op); self } - pub fn with_list_elem_qualifier(mut self, q: Option) -> Self { - self.list_elem_qualifier = q; + #[inline] + pub fn with_ops(mut self, ops: impl IntoIterator) -> Self { + self.ops.extend(ops); self } @@ -163,8 +171,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Eq, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -174,8 +181,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Ne, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -185,8 +191,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Le, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -196,8 +201,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Ge, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -207,8 +211,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Lt, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -218,8 +221,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Gt, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -229,8 +231,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), operator: FilterOperator::In, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -240,8 +241,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), operator: FilterOperator::NotIn, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -251,8 +251,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::None, operator: FilterOperator::IsNone, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -262,8 +261,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::None, operator: FilterOperator::IsSome, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -273,8 +271,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::StartsWith, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -284,8 +281,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::EndsWith, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -295,8 +291,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Contains, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -306,8 +301,7 @@ impl PropertyFilter { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::NotContains, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -325,8 +319,7 @@ impl PropertyFilter { levenshtein_distance, prefix_match, }, - list_agg: None, - list_elem_qualifier: None, + ops: vec![], _phantom: PhantomData, } } @@ -358,7 +351,11 @@ impl PropertyFilter { Ok(filter_dtype) } - fn validate(&self, dtype: &PropType, expect_map: bool) -> Result<(), GraphError> { + fn validate_operator_against_dtype( + &self, + dtype: &PropType, + expect_map: bool, + ) -> Result<(), GraphError> { match self.operator { FilterOperator::Eq | FilterOperator::Ne => { self.validate_single_dtype(dtype, expect_map)?; @@ -383,205 +380,325 @@ impl PropertyFilter { | FilterOperator::EndsWith | FilterOperator::Contains | FilterOperator::NotContains - | FilterOperator::FuzzySearch { .. } => { - match &self.prop_value { - PropertyFilterValue::None => { - return Err(GraphError::InvalidFilterExpectSingleGotNone(self.operator)) - } - PropertyFilterValue::Single(v) => { - if !matches!(dtype, PropType::Str) || !matches!(v.dtype(), PropType::Str) { - return Err(GraphError::InvalidContains(self.operator)); - } - } - PropertyFilterValue::Set(_) => { - return Err(GraphError::InvalidFilterExpectSingleGotSet(self.operator)) + | FilterOperator::FuzzySearch { .. } => match &self.prop_value { + PropertyFilterValue::None => { + return Err(GraphError::InvalidFilterExpectSingleGotNone(self.operator)) + } + PropertyFilterValue::Single(v) => { + if !matches!(dtype, PropType::Str) || !matches!(v.dtype(), PropType::Str) { + return Err(GraphError::InvalidContains(self.operator)); } - }; - } - } - Ok(()) - } - - fn validate_list_agg_operator(&self) -> Result<(), GraphError> { - if self.list_agg.is_none() { - return Ok(()); - } - match self.operator { - FilterOperator::Eq - | FilterOperator::Ne - | FilterOperator::Lt - | FilterOperator::Le - | FilterOperator::Gt - | FilterOperator::Ge - | FilterOperator::In - | FilterOperator::NotIn => Ok(()), - FilterOperator::IsSome - | FilterOperator::IsNone - | FilterOperator::StartsWith - | FilterOperator::EndsWith - | FilterOperator::Contains - | FilterOperator::NotContains - | FilterOperator::FuzzySearch { .. } => Err(GraphError::InvalidFilter(format!( - "Operator {} is not supported with list aggregation {:?}; allowed: EQ, NE, LT, LE, GT, GE, IN, NOT_IN", - self.operator, self.list_agg - ))), - } - } - - /// Effective result dtype for (agg, element PropType) as per semantic: - /// Len -> U64 - /// Avg -> F64 - /// Sum -> U64 (unsigned), I64 (signed), F64 (float) - /// Min/Max -> same as element type (numeric only) - fn effective_dtype_for_list_agg_with_inner( - agg: ListAgg, - inner: &PropType, - ) -> Result { - Ok(match agg { - ListAgg::Len => PropType::U64, - ListAgg::Avg => PropType::F64, - ListAgg::Sum => match inner { - PropType::U8 | PropType::U16 | PropType::U32 | PropType::U64 => PropType::U64, - PropType::I32 | PropType::I64 => PropType::I64, - PropType::F32 | PropType::F64 => PropType::F64, - _ => { - return Err(GraphError::InvalidFilter(format!( - "SUM requires numeric list; got element type {:?}", - inner - ))) } - }, - ListAgg::Min | ListAgg::Max => match inner { - PropType::U8 - | PropType::U16 - | PropType::U32 - | PropType::U64 - | PropType::I32 - | PropType::I64 - | PropType::F32 - | PropType::F64 => inner.clone(), // same as element type - _ => { - return Err(GraphError::InvalidFilter(format!( - "{:?} requires numeric list; got element type {:?}", - agg, inner - ))) + PropertyFilterValue::Set(_) => { + return Err(GraphError::InvalidFilterExpectSingleGotSet(self.operator)) } }, - }) + } + Ok(()) } - fn validate_list_agg_and_effective_dtype( + /// Enforce the OK/NOK matrix and compute the **effective dtype** + /// after applying the op chain (used to validate the final operator). + /// + /// Op Pair Validity Matrix (first op × second op) + /// Selector Aggregator Qualifier + /// Selector NOK OK OK + /// Aggregator NOK NOK NOK + /// Qualifier NOK OK OK + /// + /// Single-op cases (standalone): + /// Selector → OK + /// Aggregator → OK + /// Qualifier → OK + fn validate_chain_and_infer_effective_dtype( &self, - prop_dtype: &PropType, + src_dtype: &PropType, + is_temporal: bool, ) -> Result { - if let PropType::List(inner) = prop_dtype { - Self::effective_dtype_for_list_agg_with_inner(self.list_agg.unwrap(), inner) - } else { - Err(GraphError::InvalidFilter(format!( - "List aggregation {:?} is only supported on list properties; got {:?}", - self.list_agg, prop_dtype - ))) + fn agg_result_dtype( + inner: &PropType, + op: Op, + ctx: &'static str, + ) -> Result { + use PropType::*; + Ok(match op { + Op::Len => U64, + + Op::Sum => match inner { + U8 | U16 | U32 | U64 => U64, + I32 | I64 => I64, + F32 | F64 => F64, + _ => { + return Err(GraphError::InvalidFilter(format!( + "sum() {} requires numeric", + ctx + ))) + } + }, + + Op::Avg => match inner { + U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => F64, + _ => { + return Err(GraphError::InvalidFilter(format!( + "avg() {} requires numeric", + ctx + ))) + } + }, + + Op::Min | Op::Max => match inner { + U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => inner.clone(), + _ => { + return Err(GraphError::InvalidFilter(format!( + "{:?} {} requires numeric", + op, ctx + ))) + } + }, + + _ => unreachable!(), + }) } - } - fn validate_agg_single_filter_clause_prop(&self, eff: &PropType) -> Result<(), GraphError> { - let _ = self.validate_single_dtype(eff, false)?; - // If strictly numeric op, ensure the eff type can compare numerically. - if self.operator.is_strictly_numeric_operation() && !eff.has_cmp() { - return Err(GraphError::InvalidFilterCmp(eff.clone())); + if self.ops.len() > 2 { + return Err(GraphError::InvalidFilter( + "At most two list/temporal operations are allowed.".into(), + )); } - Ok(()) - } - fn validate_agg_set_filter_clause_prop(&self) -> Result<(), GraphError> { - match &self.prop_value { - PropertyFilterValue::Set(_) => Ok(()), // filter clause prop list doesn't need to be homogenous - PropertyFilterValue::Single(_) | PropertyFilterValue::None => { - Err(GraphError::InvalidFilterExpectSetGotSingle(self.operator)) + let used_qualifier = self.ops.iter().any(|o| o.is_qualifier()); + if used_qualifier && !is_temporal { + if matches!( + self.operator, + FilterOperator::IsSome | FilterOperator::IsNone + ) { + return Err(GraphError::InvalidFilter( + "Operator IS_SOME/IS_NONE is not supported with element qualifiers; apply it to the list itself (without elem qualifiers).".into() + )); } } - } - fn validate_agg(&self, dtype: &PropType) -> Result<(), GraphError> { - self.validate_list_agg_operator()?; - let agg = self.list_agg.unwrap(); - let eff = match &self.prop_ref { - PropertyRef::TemporalProperty(_, Temporal::Values) => { - Self::effective_dtype_for_list_agg_with_inner(agg, dtype)? + let used_agg = self.ops.iter().any(|o| o.is_aggregator()); + let disallowed_with_agg = matches!( + self.operator, + FilterOperator::StartsWith + | FilterOperator::EndsWith + | FilterOperator::Contains + | FilterOperator::NotContains + | FilterOperator::FuzzySearch { .. } + | FilterOperator::IsNone + | FilterOperator::IsSome + ); + if used_agg && disallowed_with_agg { + return Err(GraphError::InvalidFilter(format!( + "Operator {} is not supported with list aggregation", + self.operator + ))); + } + + let require_iterable = + |op: Op, shape_is_seq: bool, shape_is_list: bool| -> Result<(), GraphError> { + if op.is_selector() { + if !shape_is_seq { + return Err(GraphError::InvalidFilter(format!( + "{:?} requires list or temporal source", + op + ))); + } + } else if op.is_aggregator() { + if !(shape_is_seq || shape_is_list) { + return Err(GraphError::InvalidFilter(format!( + "{:?} requires list or temporal source", + op + ))); + } + } else if op.is_qualifier() { + if !(shape_is_seq || shape_is_list) { + return Err(GraphError::InvalidFilter(format!( + "{:?} requires list or temporal source", + op + ))); + } + } + Ok(()) + }; + + let (shape_is_list, elem_ty) = (matches!(src_dtype, PropType::List(_)), src_dtype.clone()); + + // Pair rules (OK/NOK) + let pair_ok = |a: Op, b: Op| -> bool { + // NOK + if a.is_selector() && b.is_selector() { + return false; + } // [Sel, Sel] + if a.is_aggregator() && (b.is_selector() || b.is_qualifier() || b.is_aggregator()) { + return false; // [Agg, Sel] / [Agg, Qual] / [Agg, Agg] } - _ => self.validate_list_agg_and_effective_dtype(dtype)?, + if a.is_qualifier() && b.is_selector() { + return false; + } // [Qual, Sel] + // OK + if a.is_selector() && (b.is_aggregator() || b.is_qualifier()) { + return true; + } // [Sel, Agg] / [Sel, Qual] + if a.is_qualifier() && (b.is_aggregator() || b.is_qualifier()) { + return true; + } // [Qual, Agg] / [Qual, Qual] + true }; - match self.operator { - FilterOperator::Eq - | FilterOperator::Ne - | FilterOperator::Lt - | FilterOperator::Le - | FilterOperator::Gt - | FilterOperator::Ge => self.validate_agg_single_filter_clause_prop(&eff), - FilterOperator::In | FilterOperator::NotIn => { - self.validate_agg_set_filter_clause_prop() - } - _ => unreachable!("blocked by validate_list_agg_operator"), - } - } + match (is_temporal, self.ops.as_slice()) { + // Catch-all: if 3 or more ops are present, reject + (false, &[_, _, _, ..]) | (true, &[_, _, _, ..]) => Err(GraphError::InvalidFilter( + "At most two list/temporal operations are allowed.".into(), + )), - fn validate_list_elem_and_operator(&self, dtype: &PropType) -> Result<(), GraphError> { - // Get the inner element type as &PropType - let inner_ty: &PropType = match dtype { - PropType::List(inner) => inner.as_ref(), // <- &PropType - _ => { - return Err(GraphError::InvalidFilter(format!( - "Element qualifier {:?} is only supported on list properties; got {:?}", - self.list_elem_qualifier, dtype - ))) + // No ops + (true, []) => { + // effective dtype: List (comparing to the full time-series) + Ok(PropType::List(Box::new(elem_ty.clone()))) } - }; + (false, []) => Ok(elem_ty), - match self.operator { - FilterOperator::IsSome | FilterOperator::IsNone => { - return Err(GraphError::InvalidFilter( - "Operator IS_SOME/IS_NONE is not supported with element qualifiers; \ - apply it to the list itself (without elem qualifiers)." - .into(), - )); + // Non-temporal: exactly one op (must be Agg or Qual) on List + (false, [op]) => { + if !op.is_aggregator() && !op.is_qualifier() { + return Err(GraphError::InvalidFilter(format!( + "Non-temporal properties support only aggregators/qualifiers; got {:?}", + op + ))); + } + if !shape_is_list { + return Err(GraphError::InvalidFilter(format!( + "{:?} requires list; property is {:?}", + op, elem_ty + ))); + } + let inner = elem_ty.inner().ok_or_else(|| { + GraphError::InvalidFilter(format!("Expected list type, got {:?}", elem_ty)) + })?; + if op.is_aggregator() { + agg_result_dtype(inner, *op, "requires numeric list") + } else { + Ok(inner.clone()) + } } - FilterOperator::In | FilterOperator::NotIn => { - if let PropertyFilterValue::Set(ref s) = self.prop_value { - for v in s.iter() { - // inner_ty is &PropType, v.dtype() is PropType - unify_types(inner_ty, &v.dtype(), &mut false) - .map_err(|e| e.with_name(self.prop_ref.name().to_owned()))?; + // Non-temporal: two ops -> not allowed + (false, [_a, _b]) => Err(GraphError::InvalidFilter( + "Non-temporal properties support at most one op.".into(), + )), + + // Temporal: one op + (true, [op]) => { + require_iterable(*op, true, shape_is_list)?; + if op.is_selector() { + // Selecting a single instant from the temporal sequence. + // If the temporal element type is T, `first/last` yields T. + // If the temporal element type is List, it yields List. + let eff = elem_ty.clone(); + return Ok(eff); + } + + let eff = if op.is_aggregator() { + if shape_is_list { + return Err(GraphError::InvalidFilter(format!( + "{:?} over temporal requires scalar elements; got List", + op + ))); } + return agg_result_dtype(&elem_ty, *op, "over time"); } else { - return Err(GraphError::InvalidFilterExpectSetGotSingle(self.operator)); - } + if let PropType::List(inner) = &elem_ty { + inner.as_ref().clone() + } else { + elem_ty.clone() + } + }; + Ok(eff) } - _ => { - // Validate single value against the element type - let dt = self.validate_single_dtype(inner_ty, false)?; - if self.operator.is_strictly_numeric_operation() && !dt.has_cmp() { - return Err(GraphError::InvalidFilterCmp(dt)); + // Temporal: two ops + (true, [a, b]) => { + if !pair_ok(*a, *b) { + return Err(GraphError::InvalidFilter(format!( + "Invalid op pair: {:?} then {:?}", + a, b + ))); } - // For string-only ops, require element type Str - if self.operator.is_string_operation() && !matches!(inner_ty, PropType::Str) { - return Err(GraphError::InvalidContains(self.operator)); + match (*a, *b) { + (sa, sb) if sa.is_selector() && sb.is_aggregator() => { + if !shape_is_list { + return Err(GraphError::InvalidFilter( + "Selector then aggregator requires Seq[List[T]] (temporal of lists)".into(), + )); + } + let inner = elem_ty.inner().ok_or_else(|| { + GraphError::InvalidFilter(format!( + "Expected list type, got {:?}", + elem_ty + )) + })?; + agg_result_dtype(inner, sb, "requires numeric") + } + (sa, sb) if sa.is_selector() && sb.is_qualifier() => { + if !shape_is_list { + return Err(GraphError::InvalidFilter( + "Selector then qualifier requires Seq[List[T]]".into(), + )); + } + let inner = elem_ty.inner().ok_or_else(|| { + GraphError::InvalidFilter(format!( + "Expected list type, got {:?}", + elem_ty + )) + })?; + Ok(inner.clone()) + } + (qa, qb) if qa.is_qualifier() && qb.is_aggregator() => { + if !shape_is_list { + return Err(GraphError::InvalidFilter( + "Qualifier then aggregator requires Seq[List[T]]".into(), + )); + } + let inner = elem_ty.inner().ok_or_else(|| { + GraphError::InvalidFilter(format!( + "Expected list type, got {:?}", + elem_ty + )) + })?; + agg_result_dtype(inner, qb, "requires numeric") + } + (qa, qb) if qa.is_qualifier() && qb.is_qualifier() => { + // Two qualifiers on a temporal property: operator applies to INNER ELEMENTS. + // We must validate the operator against the *inner element type*, not Bool. + if !shape_is_list { + return Err(GraphError::InvalidFilter( + "Two qualifiers on a temporal property require Seq[List[T]]".into(), + )); + } + let inner = elem_ty.inner().ok_or_else(|| { + GraphError::InvalidFilter(format!( + "Expected list type, got {:?}", + elem_ty + )) + })?; + Ok(inner.clone()) + } + _ => unreachable!(), } } } - - Ok(()) } pub fn resolve_prop_id(&self, meta: &Meta, expect_map: bool) -> Result { - let (name, is_static) = match &self.prop_ref { - PropertyRef::Metadata(n) => (n.as_str(), true), - PropertyRef::Property(n) | PropertyRef::TemporalProperty(n, _) => (n.as_str(), false), + let (name, is_static, is_temporal) = match &self.prop_ref { + PropertyRef::Metadata(n) => (n.as_str(), true, false), + PropertyRef::Property(n) => (n.as_str(), false, false), + PropertyRef::TemporalProperty(n) => (n.as_str(), false, true), }; - let (id, dtype) = match meta.get_prop_id_and_type(name, is_static) { + let (id, original_dtype) = match meta.get_prop_id_and_type(name, is_static) { None => { return if is_static { Err(GraphError::MetadataMissingError(name.to_string())) @@ -592,319 +709,437 @@ impl PropertyFilter { Some((id, dtype)) => (id, dtype), }; - if let (Some(agg), Some(_q)) = (self.list_agg, self.list_elem_qualifier) { - return Err(GraphError::InvalidFilter(format!( - "List aggregation {:?} cannot be used after an element qualifier (any/all).", - agg - ))); - } - - if let PropertyRef::TemporalProperty(_, Temporal::Values) = &self.prop_ref { - if let Some(_) = self.list_agg { - self.validate_agg(&dtype)?; - return Ok(id); - } - - if self.list_elem_qualifier.is_some() { - return Err(GraphError::InvalidFilter( - "Element qualifiers (any/all) are not supported with temporal aggregation." - .into(), - )); - } - - return match self.operator { - FilterOperator::Eq | FilterOperator::Ne => { - match &self.prop_value { - PropertyFilterValue::Single(Prop::List(list)) => { - // Empty list is allowed; otherwise check homogeneity & dtype compatibility - if let Some(first) = list.first() { - let first_ty = first.dtype(); - unify_types(&dtype, &first_ty, &mut false) - .map_err(|e| e.with_name(self.prop_ref.name().to_owned()))?; - for v in list.iter().skip(1) { - unify_types(&first_ty, &v.dtype(), &mut false).map_err( - |e| e.with_name(self.prop_ref.name().to_owned()), - )?; - } - } - Ok(id) - } - PropertyFilterValue::Single(_) => { - Err(GraphError::InvalidFilter( - "temporal() == / != expects a list value (e.g. [1,2,3])".into(), - )) - } - PropertyFilterValue::Set(_) | PropertyFilterValue::None => { - Err(GraphError::InvalidFilter( - "temporal() == / != expects a single list value".into(), - )) - } - } - } - _ => { - Err(GraphError::InvalidFilter( - "temporal() without aggregation supports only EQ/NE against a list (e.g. [..])." - .into(), - )) - } - } - } - - if let Some(_) = self.list_agg { - self.validate_agg(&dtype)?; - } else if let Some(_) = self.list_elem_qualifier { - self.validate_list_elem_and_operator(&dtype)?; + // Decide map-semantics for metadata + let is_original_map = matches!(original_dtype, PropType::Map(..)); + let rhs_is_map = matches!(self.prop_value, PropertyFilterValue::Single(Prop::Map(_))); + let expect_map_now = if is_static && rhs_is_map { + true } else { - self.validate(&dtype, is_static && expect_map)?; - } + is_static && expect_map && is_original_map + }; + + // Validate chain and final operator with correct semantics + let eff = self.validate_chain_and_infer_effective_dtype(&original_dtype, is_temporal)?; + // (Redundant safety) final check + self.validate_operator_against_dtype(&eff, expect_map_now)?; Ok(id) } - fn scan_u64_sum(vals: &[Prop]) -> Option<(bool, u64, u128)> { - let mut sum64: u64 = 0; - let mut sum128: u128 = 0; - let mut promoted = false; - - for p in vals { - let x = p.as_u64_lossless()?; - if !promoted { - if let Some(s) = sum64.checked_add(x) { - sum64 = s; + fn aggregate_values(vals: &[Prop], op: Op) -> Option { + fn scan_u64_sum(vals: &[Prop]) -> Option<(bool, u64, u128)> { + let mut sum64: u64 = 0; + let mut sum128: u128 = 0; + let mut promoted = false; + + for p in vals { + let x = p.as_u64_lossless()?; + if !promoted { + if let Some(s) = sum64.checked_add(x) { + sum64 = s; + } else { + promoted = true; + sum128 = (sum64 as u128) + (x as u128); + } } else { - promoted = true; - sum128 = (sum64 as u128) + (x as u128); + sum128 += x as u128; } - } else { - sum128 += x as u128; } + Some((promoted, sum64, sum128)) } - Some((promoted, sum64, sum128)) - } - fn scan_i64_sum(vals: &[Prop]) -> Option<(bool, i64, i128)> { - let mut sum64: i64 = 0; - let mut sum128: i128 = 0; - let mut promoted = false; - - for p in vals { - let x = p.as_i64_lossless()?; - if !promoted { - if let Some(s) = sum64.checked_add(x) { - sum64 = s; + fn scan_i64_sum(vals: &[Prop]) -> Option<(bool, i64, i128)> { + let mut sum64: i64 = 0; + let mut sum128: i128 = 0; + let mut promoted = false; + + for p in vals { + let x = p.as_i64_lossless()?; + if !promoted { + if let Some(s) = sum64.checked_add(x) { + sum64 = s; + } else { + promoted = true; + sum128 = (sum64 as i128) + (x as i128); + } } else { - promoted = true; - sum128 = (sum64 as i128) + (x as i128); + sum128 += x as i128; } - } else { - sum128 += x as i128; } + Some((promoted, sum64, sum128)) } - Some((promoted, sum64, sum128)) - } - - fn scan_u64_min_max(vals: &[Prop]) -> Option<(u64, u64)> { - let mut it = vals.iter(); - let first = it.next()?.as_u64_lossless()?; - let mut min_v = first; - let mut max_v = first; - for p in it { - let x = p.as_u64_lossless()?; - if x < min_v { - min_v = x; - } - if x > max_v { - max_v = x; + + fn scan_u64_min_max(vals: &[Prop]) -> Option<(u64, u64)> { + let mut it = vals.iter(); + let first = it.next()?.as_u64_lossless()?; + let mut min_v = first; + let mut max_v = first; + for p in it { + let x = p.as_u64_lossless()?; + if x < min_v { + min_v = x; + } + if x > max_v { + max_v = x; + } } + Some((min_v, max_v)) } - Some((min_v, max_v)) - } - - fn scan_i64_min_max(vals: &[Prop]) -> Option<(i64, i64)> { - let mut it = vals.iter(); - let first = it.next()?.as_i64_lossless()?; - let mut min_v = first; - let mut max_v = first; - for p in it { - let x = p.as_i64_lossless()?; - if x < min_v { - min_v = x; - } - if x > max_v { - max_v = x; + + fn scan_i64_min_max(vals: &[Prop]) -> Option<(i64, i64)> { + let mut it = vals.iter(); + let first = it.next()?.as_i64_lossless()?; + let mut min_v = first; + let mut max_v = first; + for p in it { + let x = p.as_i64_lossless()?; + if x < min_v { + min_v = x; + } + if x > max_v { + max_v = x; + } } + Some((min_v, max_v)) } - Some((min_v, max_v)) - } - fn scan_f64_sum_count(vals: &[Prop]) -> Option<(f64, u64)> { - let mut sum = 0.0f64; - let mut count = 0u64; - for p in vals { - let x = p.as_f64_lossless()?; - if !x.is_finite() { - return None; + fn scan_f64_sum_count(vals: &[Prop]) -> Option<(f64, u64)> { + let mut sum = 0.0f64; + let mut count = 0u64; + for p in vals { + let x = p.as_f64_lossless()?; + if !x.is_finite() { + return None; + } + sum += x; + count += 1; } - sum += x; - count += 1; + Some((sum, count)) } - Some((sum, count)) - } - fn scan_f64_min_max(vals: &[Prop]) -> Option<(f64, f64)> { - let mut it = vals.iter(); - let first = it.next()?.as_f64_lossless()?; - if !first.is_finite() { - return None; - } - let mut min_v = first; - let mut max_v = first; - for p in it { - let x = p.as_f64_lossless()?; - if !x.is_finite() { + fn scan_f64_min_max(vals: &[Prop]) -> Option<(f64, f64)> { + let mut it = vals.iter(); + let first = it.next()?.as_f64_lossless()?; + if !first.is_finite() { return None; } - if x < min_v { - min_v = x; - } - if x > max_v { - max_v = x; + let mut min_v = first; + let mut max_v = first; + for p in it { + let x = p.as_f64_lossless()?; + if !x.is_finite() { + return None; + } + if x < min_v { + min_v = x; + } + if x > max_v { + max_v = x; + } } + Some((min_v, max_v)) } - Some((min_v, max_v)) - } - fn reduce_unsigned(vals: &[Prop], ret_minmax: fn(u64) -> Prop, agg: ListAgg) -> Option { - match agg { - ListAgg::Sum => { - let (promoted, s64, s128) = Self::scan_u64_sum(vals)?; - Some(if promoted { - Prop::U64(u64::try_from(s128).ok()?) - } else { - Prop::U64(s64) - }) - } - ListAgg::Avg => { - let (promoted, s64, s128) = Self::scan_u64_sum(vals)?; - let count = vals.len() as u64; - let s = if promoted { s128 as f64 } else { s64 as f64 }; - Some(Prop::F64(s / (count as f64))) + fn reduce_unsigned(vals: &[Prop], ret_minmax: fn(u64) -> Prop, op: Op) -> Option { + match op { + Op::Sum => { + let (promoted, s64, s128) = scan_u64_sum(vals)?; + Some(if promoted { + Prop::U64(u64::try_from(s128).ok()?) + } else { + Prop::U64(s64) + }) + } + Op::Avg => { + let (promoted, s64, s128) = scan_u64_sum(vals)?; + let count = vals.len() as u64; + let s = if promoted { s128 as f64 } else { s64 as f64 }; + Some(Prop::F64(s / (count as f64))) + } + Op::Min => scan_u64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), + Op::Max => scan_u64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), + Op::Len | Op::First | Op::Last | Op::Any | Op::All => unreachable!(), } - ListAgg::Min => Self::scan_u64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), - ListAgg::Max => Self::scan_u64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), - ListAgg::Len => unreachable!(), } - } - fn reduce_signed(vals: &[Prop], ret_minmax: fn(i64) -> Prop, agg: ListAgg) -> Option { - match agg { - ListAgg::Sum => { - let (promoted, s64, s128) = Self::scan_i64_sum(vals)?; - Some(if promoted { - Prop::I64(i64::try_from(s128).ok()?) - } else { - Prop::I64(s64) - }) + fn reduce_signed(vals: &[Prop], ret_minmax: fn(i64) -> Prop, op: Op) -> Option { + match op { + Op::Sum => { + let (promoted, s64, s128) = scan_i64_sum(vals)?; + Some(if promoted { + Prop::I64(i64::try_from(s128).ok()?) + } else { + Prop::I64(s64) + }) + } + Op::Avg => { + let (promoted, s64, s128) = scan_i64_sum(vals)?; + let count = vals.len() as u64; + let s = if promoted { s128 as f64 } else { s64 as f64 }; + Some(Prop::F64(s / (count as f64))) + } + Op::Min => scan_i64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), + Op::Max => scan_i64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), + Op::Len | Op::First | Op::Last | Op::Any | Op::All => unreachable!(), } - ListAgg::Avg => { - let (promoted, s64, s128) = Self::scan_i64_sum(vals)?; - let count = vals.len() as u64; - let s = if promoted { s128 as f64 } else { s64 as f64 }; - Some(Prop::F64(s / (count as f64))) + } + + fn reduce_float(vals: &[Prop], ret_minmax: fn(f64) -> Prop, op: Op) -> Option { + match op { + Op::Sum => scan_f64_sum_count(vals).map(|(sum, _)| Prop::F64(sum)), + Op::Avg => { + let (sum, count) = scan_f64_sum_count(vals)?; + Some(Prop::F64(sum / (count as f64))) + } + Op::Min => scan_f64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), + Op::Max => scan_f64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), + Op::Len | Op::First | Op::Last | Op::Any | Op::All => unreachable!(), } - ListAgg::Min => Self::scan_i64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), - ListAgg::Max => Self::scan_i64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), - ListAgg::Len => unreachable!(), } - } - fn reduce_float(vals: &[Prop], ret_minmax: fn(f64) -> Prop, agg: ListAgg) -> Option { - match agg { - ListAgg::Sum => Self::scan_f64_sum_count(vals).map(|(sum, _)| Prop::F64(sum)), - ListAgg::Avg => { - let (sum, count) = Self::scan_f64_sum_count(vals)?; - Some(Prop::F64(sum / (count as f64))) + match op { + Op::Len => Some(Prop::U64(vals.len() as u64)), + Op::Sum | Op::Avg | Op::Min | Op::Max => { + if vals.is_empty() { + return None; + } + let inner = vals[0].dtype(); + match inner { + PropType::U8 => reduce_unsigned(vals, |x| Prop::U8(x as u8), op), + PropType::U16 => reduce_unsigned(vals, |x| Prop::U16(x as u16), op), + PropType::U32 => reduce_unsigned(vals, |x| Prop::U32(x as u32), op), + PropType::U64 => reduce_unsigned(vals, |x| Prop::U64(x), op), + + PropType::I32 => reduce_signed(vals, |x| Prop::I32(x as i32), op), + PropType::I64 => reduce_signed(vals, |x| Prop::I64(x), op), + + PropType::F32 => reduce_float(vals, |x| Prop::F32(x as f32), op), + PropType::F64 => reduce_float(vals, |x| Prop::F64(x), op), + _ => None, + } } - ListAgg::Min => Self::scan_f64_min_max(vals).map(|(mn, _)| ret_minmax(mn)), - ListAgg::Max => Self::scan_f64_min_max(vals).map(|(_, mx)| ret_minmax(mx)), - ListAgg::Len => unreachable!(), + Op::First | Op::Last | Op::Any | Op::All => unreachable!(), } } - fn aggregate_values(vals: &[Prop], agg: ListAgg) -> Option { - if vals.is_empty() { - return match agg { - ListAgg::Len => Some(Prop::U64(0)), - _ => None, + fn apply_two_qualifiers_temporal(&self, prop: &[Prop], outer: Op, inner: Op) -> bool { + debug_assert!(outer.is_qualifier() && inner.is_qualifier()); + + let mut per_time: Vec = Vec::with_capacity(prop.len()); + for v in prop { + // Only lists participate. Non-lists => "no elements" at that time. + let elems: &[Prop] = match v { + Prop::List(inner_vals) => inner_vals.as_slice(), + _ => &[], // <-- do NOT coerce a scalar into a 1-element list }; + + let inner_ok = match inner { + Op::Any => elems + .iter() + .any(|e| self.operator.apply_to_property(&self.prop_value, Some(e))), + Op::All => { + // All requires at least one element. + !elems.is_empty() + && elems + .iter() + .all(|e| self.operator.apply_to_property(&self.prop_value, Some(e))) + } + _ => unreachable!(), + }; + + per_time.push(inner_ok); } - if let ListAgg::Len = agg { - return Some(Prop::U64(vals.len() as u64)); + + match outer { + Op::Any => per_time.into_iter().any(|b| b), + Op::All => !per_time.is_empty() && per_time.into_iter().all(|b| b), + _ => unreachable!(), } + } - // Assume homogeneity (validated elsewhere). Use the first value's dtype. - let inner = vals[0].dtype(); - match inner { - PropType::U8 => Self::reduce_unsigned(vals, |x| Prop::U8(x as u8), agg), - PropType::U16 => Self::reduce_unsigned(vals, |x| Prop::U16(x as u16), agg), - PropType::U32 => Self::reduce_unsigned(vals, |x| Prop::U32(x as u32), agg), - PropType::U64 => Self::reduce_unsigned(vals, |x| Prop::U64(x), agg), + fn eval_ops(&self, mut state: ValueType) -> (Option, Option>, Option) { + let mut qualifier: Option = None; - PropType::I32 => Self::reduce_signed(vals, |x| Prop::I32(x as i32), agg), - PropType::I64 => Self::reduce_signed(vals, |x| Prop::I64(x), agg), + let has_later_reduce = |ops: &[Op], i: usize| -> bool { + ops.iter() + .enumerate() + .skip(i + 1) + .any(|(_, op)| op.is_selector() || op.is_aggregator() || op.is_qualifier()) + }; - PropType::F32 => Self::reduce_float(vals, |x| Prop::F32(x as f32), agg), - PropType::F64 => Self::reduce_float(vals, |x| Prop::F64(x), agg), + let flatten_one = |vals: Vec| -> Vec { + let mut out = Vec::new(); + for p in vals { + if let Prop::List(inner) = p { + out.extend(inner.as_slice().iter().cloned()); + } else { + out.push(p); + } + } + out + }; - // Non-numeric: only Len is supported (already handled). - _ => None, - } - } + let per_elem_map = |vals: Vec, op: Op| -> Vec { + vals.into_iter() + .filter_map(|p| match (op, p) { + (Op::Len, Prop::List(inner)) => Some(Prop::U64(inner.len() as u64)), + (Op::Sum, Prop::List(inner)) => { + Self::aggregate_values(inner.as_slice(), Op::Sum) + } + (Op::Avg, Prop::List(inner)) => { + Self::aggregate_values(inner.as_slice(), Op::Avg) + } + (Op::Min, Prop::List(inner)) => { + Self::aggregate_values(inner.as_slice(), Op::Min) + } + (Op::Max, Prop::List(inner)) => { + Self::aggregate_values(inner.as_slice(), Op::Max) + } + _ => None, + }) + .collect() + }; - fn aggregate_list_value(&self, list_prop: &Prop, agg: ListAgg) -> Option { - match list_prop { - Prop::List(v) => Self::aggregate_values(v.as_slice(), agg), - _ => None, + for (i, op) in self.ops.iter().enumerate() { + match *op { + Op::First => { + state = match state { + ValueType::Seq(vs) => ValueType::Scalar(vs.first().cloned()), + ValueType::Scalar(s) => ValueType::Scalar(s), + }; + } + Op::Last => { + state = match state { + ValueType::Seq(vs) => ValueType::Scalar(vs.last().cloned()), + ValueType::Scalar(s) => ValueType::Scalar(s), + }; + } + Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => { + state = match state { + ValueType::Seq(vs) => { + if matches!(vs.first(), Some(Prop::List(_))) { + ValueType::Seq(per_elem_map(vs, *op)) + } else { + ValueType::Scalar(Self::aggregate_values(&vs, *op)) + } + } + ValueType::Scalar(Some(Prop::List(inner))) => { + ValueType::Scalar(Self::aggregate_values(inner.as_slice(), *op)) + } + ValueType::Scalar(Some(_)) => ValueType::Scalar(None), + ValueType::Scalar(None) => ValueType::Scalar(None), + }; + } + Op::Any | Op::All => { + qualifier = Some(*op); + let later = has_later_reduce(&self.ops, i); + state = match state { + ValueType::Seq(vs) => { + if later { + ValueType::Seq(vs) + } else { + ValueType::Seq(flatten_one(vs)) + } + } + ValueType::Scalar(Some(Prop::List(inner))) => { + if later { + ValueType::Seq(vec![Prop::List(inner)]) + } else { + ValueType::Seq(inner.as_slice().to_vec()) + } + } + ValueType::Scalar(Some(p)) => ValueType::Seq(vec![p]), + ValueType::Scalar(None) => ValueType::Seq(vec![]), + }; + } + } } - } - fn aggregate_temporal_values<'a>( - &self, - iter: impl Iterator, - agg: ListAgg, - ) -> Option { - if matches!(agg, ListAgg::Len) { - return Some(Prop::U64(iter.into_iter().count() as u64)); + if let Some(q) = qualifier { + let elems = match state { + ValueType::Seq(vs) => vs, + ValueType::Scalar(Some(Prop::List(inner))) => inner.as_slice().to_vec(), + ValueType::Scalar(Some(p)) => vec![p], + ValueType::Scalar(None) => vec![], + }; + return (None, Some(elems), Some(q)); } - let values: Vec = iter.into_iter().collect(); - Self::aggregate_values(&values, agg) + match state { + ValueType::Scalar(v) => (v, None, None), + ValueType::Seq(vs) => (None, Some(vs), None), + } } - pub fn matches(&self, other: Option<&Prop>) -> bool { - if let Some(agg) = self.list_agg { - let agg_other = other.and_then(|x| self.aggregate_list_value(x, agg)); + fn apply_eval( + &self, + reduced: Option, + maybe_seq: Option>, + qualifier: Option, + ) -> bool { + // 1) Reduced scalar -> compare directly + // For example: + // 1. NodeFilter::property("temp").temporal().avg().ge(Prop::F64(10.0)) + // 2. NodeFilter::property("readings").temporal().first().len().eq(Prop::U64(3)) + // 3. NodeFilter::property("scores").avg().gt(Prop::F64(0.5)) + if let Some(value) = reduced { return self .operator - .apply_to_property(&self.prop_value, agg_other.as_ref()); + .apply_to_property(&self.prop_value, Some(&value)); } - if let Some(q) = self.list_elem_qualifier { - let vals = match other { - Some(Prop::List(v)) => v.as_slice(), - _ => return false, // not a list - }; + // 2) Qualifier over a sequence (ANY/ALL) + // For example: + // 1. NodeFilter::property("tags").any().eq(Prop::Str("gold".into())) + // 2. NodeFilter::property("price").temporal().any().gt(Prop::F64(100.0)) + if let Some(q) = qualifier { + let vals = maybe_seq.unwrap_or_default(); if vals.is_empty() { return false; } - let chk = |val: &Prop| self.operator.apply_to_property(&self.prop_value, Some(val)); + let chk = |v: &Prop| self.operator.apply_to_property(&self.prop_value, Some(v)); return match q { - ListElemQualifier::Any => vals.iter().any(chk), - ListElemQualifier::All => vals.iter().all(chk), + Op::Any => vals.iter().any(chk), + Op::All => vals.iter().all(chk), + _ => unreachable!(), }; } - self.operator.apply_to_property(&self.prop_value, other) + // 3) Compare whole sequence as a List, or missing value + // For example: + // 1. NodeFilter::property("temperature").temporal().eq(Prop::List(vec![...])) + // 2. NodeFilter::property("tags").eq(Prop::List(vec!["gold", "silver"])) + if let Some(seq) = maybe_seq { + let full = Prop::List(Arc::new(seq)); + self.operator + .apply_to_property(&self.prop_value, Some(&full)) + } else { + self.operator.apply_to_property(&self.prop_value, None) + } + } + + fn eval_scalar_and_apply(&self, prop: Option) -> bool { + let (reduced, maybe_seq, qualifier) = self.eval_ops(ValueType::Scalar(prop)); + self.apply_eval(reduced, maybe_seq, qualifier) + } + + fn eval_temporal_and_apply(&self, props: Vec) -> bool { + // Special-case: two qualifiers on temporal -> directly compute final bool. + // For example: + // 1. NodeFilter::property("p_flags").temporal().all().all().eq(Prop::u64(1)) + // 2. NodeFilter::property("p_flags").temporal().any().all().eq(Prop::bool(true)) + if self.ops.len() == 2 && self.ops[0].is_qualifier() && self.ops[1].is_qualifier() { + return self.apply_two_qualifiers_temporal(&props, self.ops[0], self.ops[1]); + } + let (reduced, maybe_seq, qualifier) = self.eval_ops(ValueType::Seq(props)); + self.apply_eval(reduced, maybe_seq, qualifier) + } + + pub fn matches(&self, other: Option<&Prop>) -> bool { + if self.ops.is_empty() { + return self.operator.apply_to_property(&self.prop_value, other); + } + self.eval_scalar_and_apply(other.cloned()) } fn is_property_matched( @@ -914,55 +1149,16 @@ impl PropertyFilter { ) -> bool { match self.prop_ref { PropertyRef::Property(_) => { - let prop_value = props.get_by_id(t_prop_id); - self.matches(prop_value.as_ref()) + let prop = props.get_by_id(t_prop_id); + self.matches(prop.as_ref()) } PropertyRef::Metadata(_) => false, - PropertyRef::TemporalProperty(_, Temporal::Any) => props - .temporal() - .get_by_id(t_prop_id) - .filter(|prop_view| prop_view.values().any(|v| self.matches(Some(&v)))) - .is_some(), - PropertyRef::TemporalProperty(_, Temporal::Latest) => { - let prop_value = props - .temporal() - .get_by_id(t_prop_id) - .and_then(|prop_view| prop_view.latest()); - self.matches(prop_value.as_ref()) - } - PropertyRef::TemporalProperty(_, Temporal::First) => { - let prop_value = props - .temporal() - .get_by_id(t_prop_id) - .and_then(|prop_view| prop_view.first()); - self.matches(prop_value.as_ref()) - } - PropertyRef::TemporalProperty(_, Temporal::All) => props - .temporal() - .get_by_id(t_prop_id) - .filter(|prop_view| { - let has_any = prop_view.values().next().is_some(); - let all_ok = prop_view.values().all(|v| self.matches(Some(&v))); - has_any && all_ok - }) - .is_some(), - PropertyRef::TemporalProperty(_, Temporal::Values) => { - let tview = match props.temporal().get_by_id(t_prop_id) { - Some(v) => v, - None => return false, + PropertyRef::TemporalProperty(_) => { + let Some(tview) = props.temporal().get_by_id(t_prop_id) else { + return false; }; - - if let Some(agg) = self.list_agg { - let agg_prop = self.aggregate_temporal_values(tview.values(), agg); - self.operator - .apply_to_property(&self.prop_value, agg_prop.as_ref()) - } else { - // No aggregation: compare the full temporal history as a list - let values: Vec = tview.values().collect(); - let full = Prop::List(Arc::new(values)); - self.operator - .apply_to_property(&self.prop_value, Some(&full)) - } + let props: Vec = tview.values().collect(); + self.eval_temporal_and_apply(props) } } } @@ -993,7 +1189,7 @@ impl PropertyFilter { let props = node.metadata(); self.is_metadata_matched(prop_id, props) } - PropertyRef::TemporalProperty(_, _) | PropertyRef::Property(_) => { + PropertyRef::TemporalProperty(_) | PropertyRef::Property(_) => { let props = node.properties(); self.is_property_matched(prop_id, props) } @@ -1012,7 +1208,7 @@ impl PropertyFilter { let props = edge.metadata(); self.is_metadata_matched(prop_id, props) } - PropertyRef::TemporalProperty(_, _) | PropertyRef::Property(_) => { + PropertyRef::TemporalProperty(_) | PropertyRef::Property(_) => { let props = edge.properties(); self.is_property_matched(prop_id, props) } @@ -1033,7 +1229,7 @@ impl PropertyFilter { let props = edge.metadata(); self.is_metadata_matched(prop_id, props) } - PropertyRef::TemporalProperty(_, _) | PropertyRef::Property(_) => { + PropertyRef::TemporalProperty(_) | PropertyRef::Property(_) => { let props = edge.properties(); self.is_property_matched(prop_id, props) } @@ -1094,28 +1290,18 @@ pub trait InternalPropertyFilterOps: Send + Sync { fn property_ref(&self) -> PropertyRef; - fn list_agg(&self) -> Option { - None - } - - fn list_elem_qualifier(&self) -> Option { - None + fn ops(&self) -> &[Op] { + &[] } } impl InternalPropertyFilterOps for Arc { type Marker = T::Marker; - fn property_ref(&self) -> PropertyRef { self.deref().property_ref() } - - fn list_agg(&self) -> Option { - self.deref().list_agg() - } - - fn list_elem_qualifier(&self) -> Option { - self.deref().list_elem_qualifier() + fn ops(&self) -> &[Op] { + self.deref().ops() } } @@ -1158,87 +1344,63 @@ pub trait PropertyFilterOps: InternalPropertyFilterOps { impl PropertyFilterOps for T { fn eq(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::eq(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::eq(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn ne(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::ne(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::ne(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn le(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::le(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::le(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn ge(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::ge(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::ge(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn lt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::lt(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::lt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn gt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::gt(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::gt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter::is_in(self.property_ref(), values) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::is_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) } fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter::is_not_in(self.property_ref(), values) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::is_not_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) } fn is_none(&self) -> PropertyFilter { - PropertyFilter::is_none(self.property_ref()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::is_none(self.property_ref()).with_ops(self.ops().iter().copied()) } fn is_some(&self) -> PropertyFilter { - PropertyFilter::is_some(self.property_ref()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + PropertyFilter::is_some(self.property_ref()).with_ops(self.ops().iter().copied()) } fn starts_with(&self, value: impl Into) -> PropertyFilter { PropertyFilter::starts_with(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + .with_ops(self.ops().iter().copied()) } fn ends_with(&self, value: impl Into) -> PropertyFilter { PropertyFilter::ends_with(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + .with_ops(self.ops().iter().copied()) } fn contains(&self, value: impl Into) -> PropertyFilter { PropertyFilter::contains(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + .with_ops(self.ops().iter().copied()) } fn not_contains(&self, value: impl Into) -> PropertyFilter { PropertyFilter::not_contains(self.property_ref(), value.into()) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + .with_ops(self.ops().iter().copied()) } fn fuzzy_search( @@ -1253,8 +1415,7 @@ impl PropertyFilterOps for T { levenshtein_distance, prefix_match, ) - .with_list_agg(self.list_agg()) - .with_list_elem_qualifier(self.list_elem_qualifier()) + .with_ops(self.ops().iter().copied()) } } @@ -1269,66 +1430,11 @@ impl PropertyFilterBuilder { impl InternalPropertyFilterOps for PropertyFilterBuilder { type Marker = M; - fn property_ref(&self) -> PropertyRef { PropertyRef::Property(self.0.clone()) } } -#[derive(Clone)] -pub struct AnyElemFilterBuilder(pub PropertyRef, PhantomData); - -#[derive(Clone)] -pub struct AllElemFilterBuilder(pub PropertyRef, PhantomData); - -pub trait ElemQualifierOps: InternalPropertyFilterOps { - fn any(self) -> AnyElemFilterBuilder - where - Self: Sized, - { - AnyElemFilterBuilder(self.property_ref(), PhantomData) - } - - fn all(self) -> AllElemFilterBuilder - where - Self: Sized, - { - AllElemFilterBuilder(self.property_ref(), PhantomData) - } -} - -impl ElemQualifierOps for T {} - -impl InternalPropertyFilterOps for AnyElemFilterBuilder { - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - self.0.clone() - } - - fn list_elem_qualifier(&self) -> Option { - Some(ListElemQualifier::Any) - } -} - -impl InternalPropertyFilterOps for AllElemFilterBuilder { - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - self.0.clone() - } - - fn list_elem_qualifier(&self) -> Option { - Some(ListElemQualifier::All) - } -} - -impl PropertyFilterBuilder { - pub fn temporal(self) -> TemporalPropertyFilterBuilder { - TemporalPropertyFilterBuilder(self.0, PhantomData) - } -} - #[derive(Clone)] pub struct MetadataFilterBuilder(pub String, PhantomData); @@ -1340,240 +1446,152 @@ impl MetadataFilterBuilder { impl InternalPropertyFilterOps for MetadataFilterBuilder { type Marker = M; - fn property_ref(&self) -> PropertyRef { PropertyRef::Metadata(self.0.clone()) } } #[derive(Clone)] -pub struct TemporalPropertyFilterBuilder(pub String, PhantomData); - -impl TemporalPropertyFilterBuilder { - pub fn any(self) -> AnyTemporalPropertyFilterBuilder { - AnyTemporalPropertyFilterBuilder(self.0, PhantomData) - } - - pub fn latest(self) -> LatestTemporalPropertyFilterBuilder { - LatestTemporalPropertyFilterBuilder(self.0, PhantomData) - } - - pub fn first(self) -> FirstTemporalPropertyFilterBuilder { - FirstTemporalPropertyFilterBuilder(self.0, PhantomData) - } - - pub fn all(self) -> AllTemporalPropertyFilterBuilder { - AllTemporalPropertyFilterBuilder(self.0, PhantomData) - } -} - -impl InternalPropertyFilterOps - for TemporalPropertyFilterBuilder -{ - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Values) - } - - fn list_agg(&self) -> Option { - None - } - - fn list_elem_qualifier(&self) -> Option { - None - } -} - -impl ListAggOps for TemporalPropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Values) - } -} - -#[derive(Clone)] -pub struct AnyTemporalPropertyFilterBuilder(pub String, PhantomData); - -impl InternalPropertyFilterOps - for AnyTemporalPropertyFilterBuilder -{ - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Any) - } -} - -#[derive(Clone)] -pub struct LatestTemporalPropertyFilterBuilder(pub String, PhantomData); - -impl InternalPropertyFilterOps - for LatestTemporalPropertyFilterBuilder -{ - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Latest) - } -} - -#[derive(Clone)] -pub struct FirstTemporalPropertyFilterBuilder(pub String, PhantomData); - -impl InternalPropertyFilterOps - for FirstTemporalPropertyFilterBuilder -{ - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::First) - } -} - -#[derive(Clone)] -pub struct AllTemporalPropertyFilterBuilder(pub String, PhantomData); - -impl InternalPropertyFilterOps - for AllTemporalPropertyFilterBuilder -{ - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::All) - } +pub struct OpChainBuilder { + pub prop_ref: PropertyRef, + pub ops: Vec, + pub _phantom: PhantomData, } -#[derive(Clone)] -pub struct LenFilterBuilder(pub PropertyRef, PhantomData); - -#[derive(Clone)] -pub struct SumFilterBuilder(pub PropertyRef, PhantomData); - -#[derive(Clone)] -pub struct AvgFilterBuilder(pub PropertyRef, PhantomData); - -#[derive(Clone)] -pub struct MinFilterBuilder(pub PropertyRef, PhantomData); - -#[derive(Clone)] -pub struct MaxFilterBuilder(pub PropertyRef, PhantomData); - -pub trait ListAggOps: Sized { - fn property_ref_for_self(&self) -> PropertyRef; - - fn len(self) -> LenFilterBuilder { - LenFilterBuilder(self.property_ref_for_self(), PhantomData) +impl OpChainBuilder { + pub fn with_op(mut self, op: Op) -> Self { + self.ops.push(op); + self } - fn sum(self) -> SumFilterBuilder { - SumFilterBuilder(self.property_ref_for_self(), PhantomData) + pub fn with_ops(mut self, ops: impl IntoIterator) -> Self { + self.ops.extend(ops); + self } - fn avg(self) -> AvgFilterBuilder { - AvgFilterBuilder(self.property_ref_for_self(), PhantomData) + pub fn first(self) -> Self { + self.with_op(Op::First) } - fn min(self) -> MinFilterBuilder { - MinFilterBuilder(self.property_ref_for_self(), PhantomData) + pub fn last(self) -> Self { + self.with_op(Op::Last) } - fn max(self) -> MaxFilterBuilder { - MaxFilterBuilder(self.property_ref_for_self(), PhantomData) + pub fn any(self) -> Self { + self.with_op(Op::Any) } -} -impl ListAggOps for PropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::Property(self.0.clone()) + pub fn all(self) -> Self { + self.with_op(Op::All) } -} -impl ListAggOps for MetadataFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::Metadata(self.0.clone()) + pub fn len(self) -> Self { + self.with_op(Op::Len) } -} -impl ListAggOps for AnyTemporalPropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Any) + pub fn sum(self) -> Self { + self.with_op(Op::Sum) } -} -impl ListAggOps for LatestTemporalPropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Latest) + pub fn avg(self) -> Self { + self.with_op(Op::Avg) } -} -impl ListAggOps for FirstTemporalPropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::First) + pub fn min(self) -> Self { + self.with_op(Op::Min) } -} -impl ListAggOps for AllTemporalPropertyFilterBuilder { - fn property_ref_for_self(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::All) + pub fn max(self) -> Self { + self.with_op(Op::Max) } } -impl InternalPropertyFilterOps for LenFilterBuilder { +impl InternalPropertyFilterOps for OpChainBuilder { type Marker = M; fn property_ref(&self) -> PropertyRef { - self.0.clone() + self.prop_ref.clone() } - fn list_agg(&self) -> Option { - Some(ListAgg::Len) + fn ops(&self) -> &[Op] { + &self.ops } } -impl InternalPropertyFilterOps for SumFilterBuilder { - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - self.0.clone() +pub trait ElemQualifierOps: InternalPropertyFilterOps { + fn any(self) -> OpChainBuilder + where + Self: Sized, + { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Any]).collect(), + _phantom: PhantomData, + } } - fn list_agg(&self) -> Option { - Some(ListAgg::Sum) + fn all(self) -> OpChainBuilder + where + Self: Sized, + { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::All]).collect(), + _phantom: PhantomData, + } } } +impl ElemQualifierOps for T {} -impl InternalPropertyFilterOps for AvgFilterBuilder { - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - self.0.clone() - } - - fn list_agg(&self) -> Option { - Some(ListAgg::Avg) +impl PropertyFilterBuilder { + pub fn temporal(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: PropertyRef::TemporalProperty(self.0), + ops: vec![], + _phantom: PhantomData, + } } } -impl InternalPropertyFilterOps for MinFilterBuilder { - type Marker = M; - - fn property_ref(&self) -> PropertyRef { - self.0.clone() +pub trait ListAggOps: InternalPropertyFilterOps + Sized { + fn len(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Len]).collect(), + _phantom: PhantomData, + } } - fn list_agg(&self) -> Option { - Some(ListAgg::Min) + fn sum(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Sum]).collect(), + _phantom: PhantomData, + } } -} -impl InternalPropertyFilterOps for MaxFilterBuilder { - type Marker = M; + fn avg(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Avg]).collect(), + _phantom: PhantomData, + } + } - fn property_ref(&self) -> PropertyRef { - self.0.clone() + fn min(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Min]).collect(), + _phantom: PhantomData, + } } - fn list_agg(&self) -> Option { - Some(ListAgg::Max) + fn max(self) -> OpChainBuilder { + OpChainBuilder { + prop_ref: self.property_ref(), + ops: self.ops().iter().copied().chain([Op::Max]).collect(), + _phantom: PhantomData, + } } } +impl ListAggOps for T {} diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 0d13948f17..2a896ec88c 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -4,7 +4,6 @@ use crate::python::filter::{ node_filter_builders::PyNodeFilter, property_filter_builders::{ PyMetadataFilterBuilder, PyPropertyFilterBuilder, PyPropertyFilterOps, - PyTemporalPropertyFilterBuilder, }, }; use pyo3::{ @@ -29,7 +28,6 @@ pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { 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/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index db86d612ae..7405f02820 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -4,7 +4,7 @@ use crate::{ model::{ property_filter::{ ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, - PropertyFilterBuilder, PropertyFilterOps, TemporalPropertyFilterBuilder, + OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, }, TryAsCompositeFilter, }, @@ -140,35 +140,40 @@ pub struct PyPropertyFilterOps { ops: Arc, agg: Arc, qual: Arc, + sel: Arc, } impl PyPropertyFilterOps { - fn from_parts( + fn from_parts( ops_provider: Arc, agg_provider: Arc, qual_provider: Arc, + sel_provider: Arc, ) -> Self where O: DynPropertyFilterOps + 'static, A: DynListAggOps + 'static, Q: DynElemQualifierOps + 'static, + S: DynSelectorOps + 'static, { PyPropertyFilterOps { ops: ops_provider, agg: agg_provider, qual: qual_provider, + sel: sel_provider, } } fn from_builder(builder: B) -> Self where - B: DynPropertyFilterOps + DynListAggOps + DynElemQualifierOps + 'static, + B: DynPropertyFilterOps + DynListAggOps + DynElemQualifierOps + DynSelectorOps + 'static, { let shared: Arc = Arc::new(builder); PyPropertyFilterOps { ops: shared.clone(), agg: shared.clone(), - qual: shared, + qual: shared.clone(), + sel: shared, } } } @@ -241,6 +246,14 @@ impl PyPropertyFilterOps { .fuzzy_search(prop_value, levenshtein_distance, prefix_match) } + pub fn first(&self) -> PyResult { + self.sel.first() + } + + pub fn last(&self) -> PyResult { + self.sel.last() + } + pub fn any(&self) -> PyResult { self.qual.any() } @@ -310,21 +323,25 @@ impl DynListAggOps for NoListAggOps { "List aggregation len cannot be used after an element qualifier (any/all).", )) } + fn sum(&self) -> PyResult { Err(PyTypeError::new_err( "List aggregation sum cannot be used after an element qualifier (any/all).", )) } + fn avg(&self) -> PyResult { Err(PyTypeError::new_err( "List aggregation avg cannot be used after an element qualifier (any/all).", )) } + fn min(&self) -> PyResult { Err(PyTypeError::new_err( "List aggregation min cannot be used after an element qualifier (any/all).", )) } + fn max(&self) -> PyResult { Err(PyTypeError::new_err( "List aggregation max cannot be used after an element qualifier (any/all).", @@ -332,186 +349,190 @@ impl DynListAggOps for NoListAggOps { } } +trait DynSelectorOps: Send + Sync { + fn first(&self) -> PyResult; + + fn last(&self) -> PyResult; +} + +#[derive(Clone)] +struct NoSelector; + +impl DynSelectorOps for NoSelector { + fn first(&self) -> PyResult { + Err(PyTypeError::new_err( + "first() is only valid on temporal properties.", + )) + } + + fn last(&self) -> PyResult { + Err(PyTypeError::new_err( + "last() is only valid on temporal properties.", + )) + } +} + impl DynListAggOps for T where - T: ListAggOps<::Marker> - + InternalPropertyFilterOps - + Clone - + Send - + Sync - + 'static, + T: ListAggOps + InternalPropertyFilterOps + Clone + Send + Sync + 'static, PropertyFilter<::Marker>: CreateFilter + TryAsCompositeFilter, { fn len(&self) -> PyResult { - let ops = Arc::new(>::len(self.clone())); + let ops = Arc::new(::len(self.clone())); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) } + fn sum(&self) -> PyResult { - let ops = Arc::new(>::sum(self.clone())); + let ops = Arc::new(::sum(self.clone())); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) } + fn avg(&self) -> PyResult { - let ops = Arc::new(>::avg(self.clone())); + let ops = Arc::new(::avg(self.clone())); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) } + fn min(&self) -> PyResult { - let ops = Arc::new(>::min(self.clone())); + let ops = Arc::new(::min(self.clone())); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) } + fn max(&self) -> PyResult { - let ops = Arc::new(>::max(self.clone())); + let ops = Arc::new(::max(self.clone())); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) } } -impl DynElemQualifierOps for T +trait QualifierBehavior: + InternalPropertyFilterOps + ElemQualifierOps + Clone + Send + Sync + 'static where - T: ElemQualifierOps<::Marker> - + InternalPropertyFilterOps - + Clone - + Send - + Sync - + 'static, - PropertyFilter<::Marker>: CreateFilter + TryAsCompositeFilter, + PropertyFilter: CreateFilter + TryAsCompositeFilter, { - fn any(&self) -> PyResult { - let ops = Arc::new(>::any(self.clone())); + fn build_any(&self) -> PyPropertyFilterOps; + + fn build_all(&self) -> PyPropertyFilterOps; +} + +impl QualifierBehavior for PropertyFilterBuilder +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn build_any(&self) -> PyPropertyFilterOps { + let ops = Arc::new(ElemQualifierOps::any(self.clone())); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } - fn all(&self) -> PyResult { - let ops = Arc::new(>::all(self.clone())); + + fn build_all(&self) -> PyPropertyFilterOps { + let ops = Arc::new(ElemQualifierOps::all(self.clone())); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual)) + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } } -trait DynTemporalPropertyFilterBuilderOps: Send + Sync { - fn __eq__(&self, value: Prop) -> PyFilterExpr; - - fn __ne__(&self, value: Prop) -> PyFilterExpr; - - fn any(&self) -> PyPropertyFilterOps; - - fn latest(&self) -> PyPropertyFilterOps; - - fn first(&self) -> PyPropertyFilterOps; - - fn all(&self) -> PyPropertyFilterOps; -} - -impl DynTemporalPropertyFilterBuilderOps - for TemporalPropertyFilterBuilder +impl QualifierBehavior for MetadataFilterBuilder where + M: Clone + Send + Sync + 'static, PropertyFilter: CreateFilter + TryAsCompositeFilter, { - fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.clone().eq(value))) - } - - fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.clone().ne(value))) - } - - fn any(&self) -> PyPropertyFilterOps { - PyPropertyFilterOps::from_builder(self.clone().any()) - } - - fn latest(&self) -> PyPropertyFilterOps { - PyPropertyFilterOps::from_builder(self.clone().latest()) - } - - fn first(&self) -> PyPropertyFilterOps { - PyPropertyFilterOps::from_builder(self.clone().first()) + fn build_any(&self) -> PyPropertyFilterOps { + let ops = Arc::new(ElemQualifierOps::any(self.clone())); + let agg = Arc::new(NoListAggOps); + let qual = Arc::new(NoElemQualifiers); + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } - - fn all(&self) -> PyPropertyFilterOps { - PyPropertyFilterOps::from_builder(self.clone().all()) + fn build_all(&self) -> PyPropertyFilterOps { + let ops = Arc::new(ElemQualifierOps::all(self.clone())); + let agg = Arc::new(NoListAggOps); + let qual = Arc::new(NoElemQualifiers); + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } } -#[pyclass( - frozen, - name = "TemporalPropertyFilterBuilder", - module = "raphtory.filter" -)] -#[derive(Clone)] -pub struct PyTemporalPropertyFilterBuilder { - t: Arc, - agg: Arc, -} - -#[pymethods] -impl PyTemporalPropertyFilterBuilder { - pub fn any(&self) -> PyPropertyFilterOps { - self.t.any() - } - - pub fn latest(&self) -> PyPropertyFilterOps { - self.t.latest() - } - - pub fn first(&self) -> PyPropertyFilterOps { - self.t.first() - } - - pub fn all(&self) -> PyPropertyFilterOps { - self.t.all() - } - - pub fn len(&self) -> PyResult { - self.agg.len() - } - - pub fn sum(&self) -> PyResult { - self.agg.sum() - } - - pub fn avg(&self) -> PyResult { - self.agg.avg() +impl DynSelectorOps for OpChainBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::from_builder(self.clone().first())) } - pub fn min(&self) -> PyResult { - self.agg.min() + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::from_builder(self.clone().last())) } +} - pub fn max(&self) -> PyResult { - self.agg.max() +impl QualifierBehavior for OpChainBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn build_any(&self) -> PyPropertyFilterOps { + let chain = self.clone().any(); + let ops = Arc::new(chain.clone()); + let agg = Arc::new(chain.clone()); + let qual = Arc::new(chain.clone()); + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) + } + + fn build_all(&self) -> PyPropertyFilterOps { + let chain = self.clone().all(); + let ops = Arc::new(chain.clone()); + let agg = Arc::new(chain.clone()); + let qual = Arc::new(chain.clone()); + let sel = Arc::new(NoSelector); + PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } +} - fn __eq__(&self, value: Prop) -> PyFilterExpr { - self.t.__eq__(value) +impl DynElemQualifierOps for T +where + T: QualifierBehavior, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn any(&self) -> PyResult { + Ok(self.build_any()) } - fn __ne__(&self, value: Prop) -> PyFilterExpr { - self.t.__ne__(value) + fn all(&self) -> PyResult { + Ok(self.build_all()) } } trait DynPropertyFilterBuilderOps: Send + Sync { - fn temporal(&self) -> PyTemporalPropertyFilterBuilder; + fn temporal(&self) -> PyPropertyFilterOps; } impl DynPropertyFilterBuilderOps for PropertyFilterBuilder where PropertyFilter: CreateFilter + TryAsCompositeFilter, { - fn temporal(&self) -> PyTemporalPropertyFilterBuilder { - let t = Arc::new(self.clone().temporal()) as Arc; - let agg = Arc::new(self.clone().temporal()) as Arc; - PyTemporalPropertyFilterBuilder { t, agg } + fn temporal(&self) -> PyPropertyFilterOps { + PyPropertyFilterOps::from_builder(self.clone().temporal()) } } @@ -538,6 +559,7 @@ where ops: inner.clone(), agg: inner.clone(), qual: inner.clone(), + sel: Arc::new(NoSelector), }; let child = PyPropertyFilterBuilder(inner as Arc); Bound::new(py, (child, parent)) @@ -546,7 +568,7 @@ where #[pymethods] impl PyPropertyFilterBuilder { - fn temporal(&self) -> PyTemporalPropertyFilterBuilder { + fn temporal(&self) -> PyPropertyFilterOps { self.0.temporal() } } @@ -573,6 +595,7 @@ where ops: Arc::new(self.clone()), agg: Arc::new(self.clone()), qual: Arc::new(self), + sel: Arc::new(NoSelector), }; let child = PyMetadataFilterBuilder; Bound::new(py, (child, parent)) diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index b07c7c5bfc..12a53216e5 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -190,7 +190,7 @@ impl<'a> EdgeFilterExecutor<'a> { limit: usize, offset: usize, ) -> Result>, GraphError> { - if filter.list_agg.is_some() || filter.list_elem_qualifier.is_some() { + if filter.ops.is_empty() { return fallback_filter_edges(graph, filter, limit, offset); } @@ -198,7 +198,7 @@ impl<'a> EdgeFilterExecutor<'a> { PropertyRef::Metadata(prop_name) => { self.apply_metadata_filter(graph, prop_name, filter, limit, offset) } - PropertyRef::TemporalProperty(prop_name, _) | PropertyRef::Property(prop_name) => self + PropertyRef::TemporalProperty(prop_name) | PropertyRef::Property(prop_name) => self .apply_temporal_property_filter( graph, prop_name, diff --git a/raphtory/src/search/exploded_edge_filter_executor.rs b/raphtory/src/search/exploded_edge_filter_executor.rs index e4589bd265..fb4664a271 100644 --- a/raphtory/src/search/exploded_edge_filter_executor.rs +++ b/raphtory/src/search/exploded_edge_filter_executor.rs @@ -191,7 +191,7 @@ impl<'a> ExplodedEdgeFilterExecutor<'a> { limit: usize, offset: usize, ) -> Result>, GraphError> { - if filter.list_agg.is_some() || filter.list_elem_qualifier.is_some() { + if filter.ops.is_empty() { return fallback_filter_exploded_edges(graph, filter, limit, offset); } @@ -199,7 +199,7 @@ impl<'a> ExplodedEdgeFilterExecutor<'a> { PropertyRef::Metadata(prop_name) => { self.apply_metadata_filter(graph, prop_name, filter, limit, offset) } - PropertyRef::TemporalProperty(prop_name, _) | PropertyRef::Property(prop_name) => self + PropertyRef::TemporalProperty(prop_name) | PropertyRef::Property(prop_name) => self .apply_temporal_property_filter( graph, prop_name, diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs index 7467aca878..47c083b595 100644 --- a/raphtory/src/search/mod.rs +++ b/raphtory/src/search/mod.rs @@ -1019,7 +1019,7 @@ mod test_index { .build(); create_index_fn(&graph, index_spec.clone()).unwrap(); - let filter = NodeFilter::property("p2").temporal().latest().eq(50u64); + let filter = NodeFilter::property("p2").temporal().last().eq(50u64); assert_eq!(search_nodes(&graph, filter.clone()), vec!["pometry"]); let node = graph @@ -1027,7 +1027,7 @@ mod test_index { .unwrap(); assert_eq!(index_spec, graph.get_index_spec().unwrap()); - let filter = NodeFilter::property("p1").temporal().latest().eq(100u64); + let filter = NodeFilter::property("p1").temporal().last().eq(100u64); assert_eq!(search_nodes(&graph, filter.clone()), vec!["shivam"]); node.add_metadata([("z", true)]).unwrap(); @@ -1059,7 +1059,7 @@ mod test_index { .add_edge(1, "shivam", "kapoor", [("p1", 100u64)], None) .unwrap(); assert_eq!(index_spec, graph.get_index_spec().unwrap()); - let filter = EdgeFilter::property("p1").temporal().latest().eq(100u64); + let filter = EdgeFilter::property("p1").temporal().last().eq(100u64); assert_eq!(search_edges(&graph, filter.clone()), vec!["shivam->kapoor"]); edge.add_metadata([("z", true)], None).unwrap(); diff --git a/raphtory/src/search/node_filter_executor.rs b/raphtory/src/search/node_filter_executor.rs index da8744549e..53a45022c4 100644 --- a/raphtory/src/search/node_filter_executor.rs +++ b/raphtory/src/search/node_filter_executor.rs @@ -189,7 +189,7 @@ impl<'a> NodeFilterExecutor<'a> { limit: usize, offset: usize, ) -> Result>, GraphError> { - if filter.list_agg.is_some() || filter.list_elem_qualifier.is_some() { + if !filter.ops.is_empty() { return fallback_filter_nodes(graph, filter, limit, offset); } @@ -197,7 +197,7 @@ impl<'a> NodeFilterExecutor<'a> { PropertyRef::Metadata(prop_name) => { self.apply_metadata_filter(graph, prop_name, filter, limit, offset) } - PropertyRef::TemporalProperty(prop_name, _) | PropertyRef::Property(prop_name) => self + PropertyRef::TemporalProperty(prop_name) | PropertyRef::Property(prop_name) => self .apply_temporal_property_filter( graph, prop_name, From 61145c9af444e4505ce51d8d215face4058933be Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:55:53 +0100 Subject: [PATCH 02/42] takes a reference --- python/python/raphtory/__init__.pyi | 56 +++++++++++++++++++ .../python/raphtory/algorithms/__init__.pyi | 3 + raphtory-graphql/schema.graphql | 2 +- .../views/filter/model/property_filter.rs | 14 ++--- .../python/filter/property_filter_builders.rs | 18 +++--- 5 files changed, 76 insertions(+), 17 deletions(-) diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi index e7d3659c82..0a17666de6 100644 --- a/python/python/raphtory/__init__.pyi +++ b/python/python/raphtory/__init__.pyi @@ -49,6 +49,7 @@ __all__ = [ "IndexSpec", "Prop", "version", + "DiskGraphStorage", "graphql", "algorithms", "graph_loader", @@ -1375,6 +1376,17 @@ class Graph(GraphView): MutableNode: The node object with the specified id, or None if the node does not exist """ + def persist_as_disk_graph(self, graph_dir: str | PathLike) -> DiskGraphStorage: + """ + save graph in disk_graph format and memory map the result + + Arguments: + graph_dir (str | PathLike): folder where the graph will be saved + + Returns: + DiskGraphStorage: the persisted graph storage + """ + def persistent_graph(self) -> PersistentGraph: """ View graph with persistent semantics @@ -1412,6 +1424,17 @@ class Graph(GraphView): bytes: """ + def to_disk_graph(self, graph_dir: str | PathLike) -> Graph: + """ + Persist graph on disk + + Arguments: + graph_dir (str | PathLike): the folder where the graph will be persisted + + Returns: + Graph: a view of the persisted graph + """ + def to_parquet(self, graph_dir: str | PathLike): """ Persist graph to parquet files @@ -2186,6 +2209,7 @@ class PersistentGraph(GraphView): bytes: """ + def to_disk_graph(self, graph_dir): ... def update_metadata(self, metadata: dict) -> None: """ Updates metadata of the graph. @@ -6118,3 +6142,35 @@ class Prop(object): def u8(value): ... def version(): ... + +class DiskGraphStorage(object): + def __repr__(self): + """Return repr(self).""" + + def append_node_temporal_properties(self, location, chunk_size=20000000): ... + def graph_dir(self): ... + @staticmethod + def load_from_dir(graph_dir): ... + @staticmethod + def load_from_pandas(graph_dir, edge_df, time_col, src_col, dst_col): ... + @staticmethod + def load_from_parquets( + graph_dir, + layer_parquet_cols, + node_properties=None, + chunk_size=10000000, + t_props_chunk_size=10000000, + num_threads=4, + node_type_col=None, + node_id_col=None, + ): ... + def load_node_metadata(self, location, col_names=None, chunk_size=None): ... + def load_node_types(self, location, col_name, chunk_size=None): ... + def merge_by_sorted_gids(self, other, graph_dir): + """ + Merge this graph with another `DiskGraph`. Note that both graphs should have nodes that are + sorted by their global ids or the resulting graph will be nonsense! + """ + + def to_events(self): ... + def to_persistent(self): ... diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi index 3873e9b001..02ea5eba3e 100644 --- a/python/python/raphtory/algorithms/__init__.pyi +++ b/python/python/raphtory/algorithms/__init__.pyi @@ -70,6 +70,7 @@ __all__ = [ "max_weight_matching", "Matching", "Infected", + "connected_components", ] def dijkstra_single_source_shortest_paths( @@ -893,3 +894,5 @@ class Infected(object): Returns: int: """ + +def connected_components(graph): ... diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index f95736a85f..d02a95e9f6 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -876,8 +876,8 @@ type Graph { } type GraphAlgorithmPlugin { - pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! shortest_path(source: String!, targets: [String!]!, direction: String): [ShortestPathOutput!]! + pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! } type GraphSchema { 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 b142c36847..01ae2ad710 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -1519,7 +1519,7 @@ impl InternalPropertyFilterOps for OpChainBuil } pub trait ElemQualifierOps: InternalPropertyFilterOps { - fn any(self) -> OpChainBuilder + fn any(&self) -> OpChainBuilder where Self: Sized, { @@ -1530,7 +1530,7 @@ pub trait ElemQualifierOps: InternalPropertyFilterOps { } } - fn all(self) -> OpChainBuilder + fn all(&self) -> OpChainBuilder where Self: Sized, { @@ -1554,7 +1554,7 @@ impl PropertyFilterBuilder { } pub trait ListAggOps: InternalPropertyFilterOps + Sized { - fn len(self) -> OpChainBuilder { + fn len(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Len]).collect(), @@ -1562,7 +1562,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } - fn sum(self) -> OpChainBuilder { + fn sum(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Sum]).collect(), @@ -1570,7 +1570,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } - fn avg(self) -> OpChainBuilder { + fn avg(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Avg]).collect(), @@ -1578,7 +1578,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } - fn min(self) -> OpChainBuilder { + fn min(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Min]).collect(), @@ -1586,7 +1586,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } - fn max(self) -> OpChainBuilder { + fn max(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Max]).collect(), diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index 7405f02820..9c731c3f11 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -378,7 +378,7 @@ where PropertyFilter<::Marker>: CreateFilter + TryAsCompositeFilter, { fn len(&self) -> PyResult { - let ops = Arc::new(::len(self.clone())); + let ops = Arc::new(::len(&self)); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -386,7 +386,7 @@ where } fn sum(&self) -> PyResult { - let ops = Arc::new(::sum(self.clone())); + let ops = Arc::new(::sum(&self)); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -394,7 +394,7 @@ where } fn avg(&self) -> PyResult { - let ops = Arc::new(::avg(self.clone())); + let ops = Arc::new(::avg(&self)); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -402,7 +402,7 @@ where } fn min(&self) -> PyResult { - let ops = Arc::new(::min(self.clone())); + let ops = Arc::new(::min(&self)); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -410,7 +410,7 @@ where } fn max(&self) -> PyResult { - let ops = Arc::new(::max(self.clone())); + let ops = Arc::new(::max(&self)); let agg = Arc::new(self.clone()); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -434,7 +434,7 @@ where PropertyFilter: CreateFilter + TryAsCompositeFilter, { fn build_any(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::any(self.clone())); + let ops = Arc::new(ElemQualifierOps::any(self)); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -442,7 +442,7 @@ where } fn build_all(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::all(self.clone())); + let ops = Arc::new(ElemQualifierOps::all(self)); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); @@ -456,14 +456,14 @@ where PropertyFilter: CreateFilter + TryAsCompositeFilter, { fn build_any(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::any(self.clone())); + let ops = Arc::new(ElemQualifierOps::any(self)); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); PyPropertyFilterOps::from_parts(ops, agg, qual, sel) } fn build_all(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::all(self.clone())); + let ops = Arc::new(ElemQualifierOps::all(self)); let agg = Arc::new(NoListAggOps); let qual = Arc::new(NoElemQualifiers); let sel = Arc::new(NoSelector); From 261385b0128f36f5968fa7aa58ed9dd3559f22b7 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:42:04 +0100 Subject: [PATCH 03/42] rework validation --- .../views/filter/model/property_filter.rs | 1007 ++++++++++------- 1 file changed, 572 insertions(+), 435 deletions(-) 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 01ae2ad710..dc94510613 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -49,7 +49,7 @@ pub enum Op { Avg, Min, Max, - // Qualifiers + // Qualifiers (quantifiers) Any, All, } @@ -59,18 +59,100 @@ impl Op { pub fn is_selector(self) -> bool { matches!(self, Op::First | Op::Last) } - #[inline] pub fn is_aggregator(self) -> bool { matches!(self, Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max) } - #[inline] pub fn is_qualifier(self) -> bool { matches!(self, Op::Any | Op::All) } } +#[derive(Clone, Debug, PartialEq, Eq)] +enum Shape { + Scalar(PropType), + List(Box), + Seq(Box), + /// A pending quantifier (any/all) over elements of the inner shape. + /// We keep the element dtype available so the final operator can be + /// validated against element type (predicate-after-quantifier). + Quantified(Box, Op), +} + +impl Shape { + #[inline] + fn elem_dtype(&self) -> Option { + match self { + Shape::Scalar(t) => Some(t.clone()), + Shape::List(t) => Some(*t.clone()), + Shape::Seq(inner) => inner.elem_dtype(), + Shape::Quantified(inner, _) => inner.elem_dtype(), + } + } +} + +/// Count consecutive Quantified wrappers and return (base shape, depth). +fn flatten_quantified_depth<'a>(mut s: &'a Shape) -> (&'a Shape, usize) { + let mut depth = 0; + while let Shape::Quantified(inner, _) = s { + depth += 1; + s = inner; + } + (s, depth) +} + +/// Unwrap `n` list layers from a PropType::List nesting. +fn unwrap_list_n(mut t: PropType, mut n: usize) -> Option { + while n > 0 { + if let PropType::List(inner) = t { + t = *inner; + n -= 1; + } else { + return None; + } + } + Some(t) +} + +#[inline] +fn agg_result_dtype(inner: &PropType, op: Op, ctx: &str) -> Result { + use PropType::*; + Ok(match op { + Op::Len => U64, + Op::Sum => match inner { + U8 | U16 | U32 | U64 => U64, + I32 | I64 => I64, + F32 | F64 => F64, + _ => { + return Err(GraphError::InvalidFilter(format!( + "sum() {} requires numeric", + ctx + ))) + } + }, + Op::Avg => match inner { + U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => F64, + _ => { + return Err(GraphError::InvalidFilter(format!( + "avg() {} requires numeric", + ctx + ))) + } + }, + Op::Min | Op::Max => match inner { + U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => inner.clone(), + _ => { + return Err(GraphError::InvalidFilter(format!( + "{:?} {} requires numeric", + op, ctx + ))) + } + }, + _ => unreachable!(), + }) +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum PropertyRef { Property(String), @@ -110,7 +192,7 @@ pub struct PropertyFilter { pub prop_ref: PropertyRef, pub prop_value: PropertyFilterValue, pub operator: FilterOperator, - pub ops: Vec, // at most 2 (validated) + pub ops: Vec, // validated by validate_chain_and_infer_effective_dtype pub _phantom: PhantomData, } @@ -356,6 +438,23 @@ impl PropertyFilter { dtype: &PropType, expect_map: bool, ) -> Result<(), GraphError> { + if self.ops.iter().copied().any(Op::is_aggregator) { + match self.operator { + FilterOperator::StartsWith + | FilterOperator::EndsWith + | FilterOperator::Contains + | FilterOperator::NotContains + | FilterOperator::IsNone + | FilterOperator::IsSome => { + return Err(GraphError::InvalidFilter(format!( + "Operator {} is not supported with list aggregation", + self.operator + ))); + } + _ => {} + } + } + match self.operator { FilterOperator::Eq | FilterOperator::Ne => { self.validate_single_dtype(dtype, expect_map)?; @@ -394,301 +493,222 @@ impl PropertyFilter { } }, } + Ok(()) } - /// Enforce the OK/NOK matrix and compute the **effective dtype** - /// after applying the op chain (used to validate the final operator). - /// - /// Op Pair Validity Matrix (first op × second op) - /// Selector Aggregator Qualifier - /// Selector NOK OK OK - /// Aggregator NOK NOK NOK - /// Qualifier NOK OK OK - /// - /// Single-op cases (standalone): - /// Selector → OK - /// Aggregator → OK - /// Qualifier → OK + /// Validate the op chain via a **right-to-left** fold over a Shape model and + /// return the effective dtype against which the final operator is checked. + /// Selectors (`first/last`) are applied *before* any other ops regardless of + /// source order to ensure chains like `.temporal().first().avg()` work as intended. fn validate_chain_and_infer_effective_dtype( &self, src_dtype: &PropType, is_temporal: bool, ) -> Result { - fn agg_result_dtype( - inner: &PropType, - op: Op, - ctx: &'static str, - ) -> Result { - use PropType::*; - Ok(match op { - Op::Len => U64, - - Op::Sum => match inner { - U8 | U16 | U32 | U64 => U64, - I32 | I64 => I64, - F32 | F64 => F64, - _ => { - return Err(GraphError::InvalidFilter(format!( - "sum() {} requires numeric", - ctx - ))) - } - }, + use Shape::*; - Op::Avg => match inner { - U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => F64, - _ => { + // Build the base shape from the underlying property type. + let base_shape: Shape = if is_temporal { + let inner: Shape = match src_dtype { + PropType::List(inner) => List(inner.clone()), + t => Scalar(t.clone()), + }; + Seq(Box::new(inner)) + } else { + match src_dtype { + PropType::List(inner) => List(inner.clone()), + t => Scalar(t.clone()), + } + }; + + #[inline] + fn agg_out(inner: &PropType, op: Op, ctx: &str) -> Result { + agg_result_dtype(inner, op, ctx) + } + + // ---- IMPORTANT: selector precedence -------------------------------------- + // To preserve RTL validation but apply selectors first, we reorder ops so that + // selectors come *last* in the vector (closest to the source), which means they + // are processed *first* when we iterate in reverse. + let mut selectors: Vec = Vec::new(); + let mut others: Vec = Vec::new(); + for &op in &self.ops { + if op.is_selector() { + selectors.push(op); + } else { + others.push(op); + } + } + let mut ops_for_validation: Vec = Vec::with_capacity(self.ops.len()); + ops_for_validation.extend(others); + ops_for_validation.extend(selectors); + // --------------------------------------------------------------------------- + + // Right-to-left fold over ops_for_validation. + let mut shape = base_shape; + for &op in ops_for_validation.iter().rev() { + shape = match op { + // --- Selectors collapse one temporal layer --- + Op::First | Op::Last => match shape { + Seq(inner) => *inner, + other => { return Err(GraphError::InvalidFilter(format!( - "avg() {} requires numeric", - ctx + "{:?} requires temporal sequence (Seq), got {:?}", + op, other ))) } }, - Op::Min | Op::Max => match inner { - U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 => inner.clone(), + // --- Quantifiers wrap current list level (or per-time list). + // Allow singleton policy for scalars. + Op::Any | Op::All => match shape { + List(_) => Quantified(Box::new(shape), op), + + Seq(inner) => match *inner { + List(_) | Quantified(_, _) => { + Seq(Box::new(Quantified(Box::new(*inner), op))) + } + Scalar(t) => Seq(Box::new(Quantified(Box::new(Scalar(t)), op))), + other => { + return Err(GraphError::InvalidFilter(format!( + "{:?} requires list elements over time; got Seq({:?})", + op, other + ))) + } + }, + + Scalar(t) => Quantified(Box::new(Scalar(t)), op), + _ => { return Err(GraphError::InvalidFilter(format!( - "{:?} {} requires numeric", - op, ctx + "{:?} requires a list (or temporal list); got {:?}", + op, shape ))) } }, - _ => unreachable!(), - }) - } - - if self.ops.len() > 2 { - return Err(GraphError::InvalidFilter( - "At most two list/temporal operations are allowed.".into(), - )); - } - - let used_qualifier = self.ops.iter().any(|o| o.is_qualifier()); - if used_qualifier && !is_temporal { - if matches!( - self.operator, - FilterOperator::IsSome | FilterOperator::IsNone - ) { - return Err(GraphError::InvalidFilter( - "Operator IS_SOME/IS_NONE is not supported with element qualifiers; apply it to the list itself (without elem qualifiers).".into() - )); - } - } - - let used_agg = self.ops.iter().any(|o| o.is_aggregator()); - let disallowed_with_agg = matches!( - self.operator, - FilterOperator::StartsWith - | FilterOperator::EndsWith - | FilterOperator::Contains - | FilterOperator::NotContains - | FilterOperator::FuzzySearch { .. } - | FilterOperator::IsNone - | FilterOperator::IsSome - ); - if used_agg && disallowed_with_agg { - return Err(GraphError::InvalidFilter(format!( - "Operator {} is not supported with list aggregation", - self.operator - ))); - } - - let require_iterable = - |op: Op, shape_is_seq: bool, shape_is_list: bool| -> Result<(), GraphError> { - if op.is_selector() { - if !shape_is_seq { - return Err(GraphError::InvalidFilter(format!( - "{:?} requires list or temporal source", - op - ))); + // --- Aggregators map list/temporal(list|scalar) to a scalar dtype --- + Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => match shape { + // Aggregate a plain list + List(inner) => { + let t = *inner; + let out = agg_out(&t, op, "over list")?; + Scalar(out) } - } else if op.is_aggregator() { - if !(shape_is_seq || shape_is_list) { - return Err(GraphError::InvalidFilter(format!( - "{:?} requires list or temporal source", - op - ))); + + // Aggregate over time + Seq(inner) => match *inner { + Scalar(t) => { + let out = agg_out(&t, op, "over time")?; + Scalar(out) + } + List(t) => { + let elem_t = *t; + let out = agg_out(&elem_t, op, "over time of lists")?; + Scalar(out) + } + // time series with quantifier on the payload: preserve the quantifier, + // map the inner list dtype through the aggregator. + Quantified(qinner, qop) => { + let mapped_inner = match *qinner { + List(t) => { + let out = + agg_out(&*t, op, "under temporal quantifier over list")?; + Scalar(out) + } + Scalar(t) => { + return Err(GraphError::InvalidFilter(format!( + "{:?} under temporal quantifier requires list elements; got Scalar({:?})", + op, t + ))); + } + Seq(_) | Quantified(_, _) => unreachable!(), + }; + Seq(Box::new(Quantified(Box::new(mapped_inner), qop))) + } + Seq(_) => unreachable!(), + }, + + // Aggregator under a non-temporal quantifier: preserve the quantifier. + Quantified(qinner, qop) => { + let mapped_inner = match *qinner { + List(t) => { + let out = agg_out(&*t, op, "under quantifier over list")?; + Scalar(out) + } + Scalar(t) => { + return Err(GraphError::InvalidFilter(format!( + "{:?} under quantifier requires list elements; got Scalar({:?})", + op, t + ))); + } + Seq(_) | Quantified(_, _) => unreachable!(), + }; + Quantified(Box::new(mapped_inner), qop) } - } else if op.is_qualifier() { - if !(shape_is_seq || shape_is_list) { + + // Aggregators require a collection or temporal sequence before them. + Scalar(t) => { return Err(GraphError::InvalidFilter(format!( - "{:?} requires list or temporal source", - op + "{:?} requires list or temporal sequence, got Scalar({:?})", + op, t ))); } - } - Ok(()) + }, }; + } - let (shape_is_list, elem_ty) = (matches!(src_dtype, PropType::List(_)), src_dtype.clone()); - - // Pair rules (OK/NOK) - let pair_ok = |a: Op, b: Op| -> bool { - // NOK - if a.is_selector() && b.is_selector() { - return false; - } // [Sel, Sel] - if a.is_aggregator() && (b.is_selector() || b.is_qualifier() || b.is_aggregator()) { - return false; // [Agg, Sel] / [Agg, Qual] / [Agg, Agg] - } - if a.is_qualifier() && b.is_selector() { - return false; - } // [Qual, Sel] - // OK - if a.is_selector() && (b.is_aggregator() || b.is_qualifier()) { - return true; - } // [Sel, Agg] / [Sel, Qual] - if a.is_qualifier() && (b.is_aggregator() || b.is_qualifier()) { - return true; - } // [Qual, Agg] / [Qual, Qual] - true - }; - - match (is_temporal, self.ops.as_slice()) { - // Catch-all: if 3 or more ops are present, reject - (false, &[_, _, _, ..]) | (true, &[_, _, _, ..]) => Err(GraphError::InvalidFilter( - "At most two list/temporal operations are allowed.".into(), - )), - - // No ops - (true, []) => { - // effective dtype: List (comparing to the full time-series) - Ok(PropType::List(Box::new(elem_ty.clone()))) - } - (false, []) => Ok(elem_ty), - - // Non-temporal: exactly one op (must be Agg or Qual) on List - (false, [op]) => { - if !op.is_aggregator() && !op.is_qualifier() { - return Err(GraphError::InvalidFilter(format!( - "Non-temporal properties support only aggregators/qualifiers; got {:?}", - op - ))); - } - if !shape_is_list { - return Err(GraphError::InvalidFilter(format!( - "{:?} requires list; property is {:?}", - op, elem_ty - ))); - } - let inner = elem_ty.inner().ok_or_else(|| { - GraphError::InvalidFilter(format!("Expected list type, got {:?}", elem_ty)) - })?; - if op.is_aggregator() { - agg_result_dtype(inner, *op, "requires numeric list") - } else { - Ok(inner.clone()) - } - } - - // Non-temporal: two ops -> not allowed - (false, [_a, _b]) => Err(GraphError::InvalidFilter( - "Non-temporal properties support at most one op.".into(), - )), - - // Temporal: one op - (true, [op]) => { - require_iterable(*op, true, shape_is_list)?; - if op.is_selector() { - // Selecting a single instant from the temporal sequence. - // If the temporal element type is T, `first/last` yields T. - // If the temporal element type is List, it yields List. - let eff = elem_ty.clone(); - return Ok(eff); - } - - let eff = if op.is_aggregator() { - if shape_is_list { - return Err(GraphError::InvalidFilter(format!( - "{:?} over temporal requires scalar elements; got List", - op - ))); - } - return agg_result_dtype(&elem_ty, *op, "over time"); - } else { - if let PropType::List(inner) = &elem_ty { - inner.as_ref().clone() - } else { - elem_ty.clone() + // Effective dtype seen by the final operator. + let effective_dtype: PropType = match shape { + Scalar(t) => t, + List(t) => PropType::List(t), + + // Non-temporal quantified input: + // Any depth of quantifiers over a list exposes the element dtype. + // Any depth over a scalar (singleton policy) exposes the scalar dtype. + Quantified(_, _) => { + let (base, _qdepth) = flatten_quantified_depth(&shape); + match base { + List(t) => (**t).clone(), + Scalar(t) => t.clone(), + _ => { + return Err(GraphError::InvalidFilter( + "Quantifier requires list or scalar input".into(), + )) } - }; - Ok(eff) + } } - // Temporal: two ops - (true, [a, b]) => { - if !pair_ok(*a, *b) { - return Err(GraphError::InvalidFilter(format!( - "Invalid op pair: {:?} then {:?}", - a, b - ))); - } - match (*a, *b) { - (sa, sb) if sa.is_selector() && sb.is_aggregator() => { - if !shape_is_list { - return Err(GraphError::InvalidFilter( - "Selector then aggregator requires Seq[List[T]] (temporal of lists)".into(), - )); - } - let inner = elem_ty.inner().ok_or_else(|| { - GraphError::InvalidFilter(format!( - "Expected list type, got {:?}", - elem_ty - )) - })?; - agg_result_dtype(inner, sb, "requires numeric") - } - (sa, sb) if sa.is_selector() && sb.is_qualifier() => { - if !shape_is_list { - return Err(GraphError::InvalidFilter( - "Selector then qualifier requires Seq[List[T]]".into(), - )); - } - let inner = elem_ty.inner().ok_or_else(|| { - GraphError::InvalidFilter(format!( - "Expected list type, got {:?}", - elem_ty - )) - })?; - Ok(inner.clone()) - } - (qa, qb) if qa.is_qualifier() && qb.is_aggregator() => { - if !shape_is_list { + // Temporal payload + Seq(inner) => match *inner { + // time series of scalars -> List effective dtype + Scalar(t) => PropType::List(Box::new(t)), + // time series of lists -> List> effective dtype + List(t) => PropType::List(Box::new(PropType::List(t))), + + // Quantified per-time payload: same expose-as-element/singleton semantics. + Quantified(_, _) => { + let (base, _qdepth) = flatten_quantified_depth(&*inner); + match base { + List(t) => (**t).clone(), + Scalar(t) => t.clone(), + _ => { return Err(GraphError::InvalidFilter( - "Qualifier then aggregator requires Seq[List[T]]".into(), - )); - } - let inner = elem_ty.inner().ok_or_else(|| { - GraphError::InvalidFilter(format!( - "Expected list type, got {:?}", - elem_ty + "Temporal quantifier requires list or scalar elements per time" + .into(), )) - })?; - agg_result_dtype(inner, qb, "requires numeric") - } - (qa, qb) if qa.is_qualifier() && qb.is_qualifier() => { - // Two qualifiers on a temporal property: operator applies to INNER ELEMENTS. - // We must validate the operator against the *inner element type*, not Bool. - if !shape_is_list { - return Err(GraphError::InvalidFilter( - "Two qualifiers on a temporal property require Seq[List[T]]".into(), - )); } - let inner = elem_ty.inner().ok_or_else(|| { - GraphError::InvalidFilter(format!( - "Expected list type, got {:?}", - elem_ty - )) - })?; - Ok(inner.clone()) } - _ => unreachable!(), } - } - } + Seq(_) => unreachable!(), + }, + }; + + // NOTE: Do **not** validate the final operator here. + // The caller (resolve_prop_id) will validate using the correct `expect_map_now` + // to support metadata map semantics. + Ok(effective_dtype) } pub fn resolve_prop_id(&self, meta: &Meta, expect_map: bool) -> Result { @@ -922,152 +942,270 @@ impl PropertyFilter { } } - fn apply_two_qualifiers_temporal(&self, prop: &[Prop], outer: Op, inner: Op) -> bool { - debug_assert!(outer.is_qualifier() && inner.is_qualifier()); - - let mut per_time: Vec = Vec::with_capacity(prop.len()); - for v in prop { - // Only lists participate. Non-lists => "no elements" at that time. - let elems: &[Prop] = match v { - Prop::List(inner_vals) => inner_vals.as_slice(), - _ => &[], // <-- do NOT coerce a scalar into a 1-element list - }; - - let inner_ok = match inner { - Op::Any => elems - .iter() - .any(|e| self.operator.apply_to_property(&self.prop_value, Some(e))), - Op::All => { - // All requires at least one element. - !elems.is_empty() - && elems - .iter() - .all(|e| self.operator.apply_to_property(&self.prop_value, Some(e))) + /// Recursive reducer for nested quantifiers: applies the final predicate + /// at the innermost level and bubbles results back using each quantifier. + fn reduce_qualifiers_rec( + &self, + quals: &[Op], + v: &Prop, + predicate: &dyn Fn(&Prop) -> bool, + ) -> bool { + if quals.is_empty() { + return predicate(v); + } + let (q, rest) = (quals[0], &quals[1..]); + + // If we have a list, descend normally. + if let Prop::List(inner) = v { + let elems = inner.as_slice(); + let check = |e: &Prop| { + if rest.is_empty() { + predicate(e) + } else { + self.reduce_qualifiers_rec(rest, e, predicate) } + }; + return match q { + Op::Any => elems.iter().any(check), + Op::All => !elems.is_empty() && elems.iter().all(check), _ => unreachable!(), }; - - per_time.push(inner_ok); } - match outer { - Op::Any => per_time.into_iter().any(|b| b), - Op::All => !per_time.is_empty() && per_time.into_iter().all(|b| b), - _ => unreachable!(), + // No list at this level: apply singleton semantics. + // Consuming ANY/ALL over a single value is equivalent to just + // continuing with the remaining quantifiers on the same value. + if rest.is_empty() { + // Last quantifier => apply to the single value. + return match q { + Op::Any | Op::All => predicate(v), + _ => unreachable!(), + }; } - } + // Still have deeper quantifiers: keep consuming them on the same value. + self.reduce_qualifiers_rec(rest, v, predicate) + } + + /// Apply a list-style aggregator to a single Prop, with singleton semantics for scalars. + fn apply_agg_to_prop(p: &Prop, op: Op) -> Option { + match (op, p) { + // ----- Lists ----- + (Op::Len, Prop::List(inner)) => Some(Prop::U64(inner.len() as u64)), + (Op::Sum, Prop::List(inner)) + | (Op::Avg, Prop::List(inner)) + | (Op::Min, Prop::List(inner)) + | (Op::Max, Prop::List(inner)) => Self::aggregate_values(inner.as_slice(), op), + + // ----- Scalars (singleton semantics) ----- + (Op::Len, _) => Some(Prop::U64(1)), + + (Op::Sum, Prop::U8(x)) => Some(Prop::U8(*x)), + (Op::Sum, Prop::U16(x)) => Some(Prop::U16(*x)), + (Op::Sum, Prop::U32(x)) => Some(Prop::U32(*x)), + (Op::Sum, Prop::U64(x)) => Some(Prop::U64(*x)), + (Op::Sum, Prop::I32(x)) => Some(Prop::I32(*x)), + (Op::Sum, Prop::I64(x)) => Some(Prop::I64(*x)), + (Op::Sum, Prop::F32(x)) => { + if x.is_finite() { + Some(Prop::F32(*x)) + } else { + None + } + } + (Op::Sum, Prop::F64(x)) => { + if x.is_finite() { + Some(Prop::F64(*x)) + } else { + None + } + } - fn eval_ops(&self, mut state: ValueType) -> (Option, Option>, Option) { - let mut qualifier: Option = None; + (Op::Avg, Prop::U8(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::U16(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::U32(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::U64(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::I32(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::I64(x)) => Some(Prop::F64(*x as f64)), + (Op::Avg, Prop::F32(x)) => { + if x.is_finite() { + Some(Prop::F32(*x)) + } else { + None + } + } + (Op::Avg, Prop::F64(x)) => { + if x.is_finite() { + Some(Prop::F64(*x)) + } else { + None + } + } - let has_later_reduce = |ops: &[Op], i: usize| -> bool { - ops.iter() - .enumerate() - .skip(i + 1) - .any(|(_, op)| op.is_selector() || op.is_aggregator() || op.is_qualifier()) - }; + (Op::Min, Prop::U8(x)) => Some(Prop::U8(*x)), + (Op::Min, Prop::U16(x)) => Some(Prop::U16(*x)), + (Op::Min, Prop::U32(x)) => Some(Prop::U32(*x)), + (Op::Min, Prop::U64(x)) => Some(Prop::U64(*x)), + (Op::Min, Prop::I32(x)) => Some(Prop::I32(*x)), + (Op::Min, Prop::I64(x)) => Some(Prop::I64(*x)), + (Op::Min, Prop::F32(x)) => { + if x.is_finite() { + Some(Prop::F32(*x)) + } else { + None + } + } + (Op::Min, Prop::F64(x)) => { + if x.is_finite() { + Some(Prop::F64(*x)) + } else { + None + } + } - let flatten_one = |vals: Vec| -> Vec { - let mut out = Vec::new(); - for p in vals { - if let Prop::List(inner) = p { - out.extend(inner.as_slice().iter().cloned()); + (Op::Max, Prop::U8(x)) => Some(Prop::U8(*x)), + (Op::Max, Prop::U16(x)) => Some(Prop::U16(*x)), + (Op::Max, Prop::U32(x)) => Some(Prop::U32(*x)), + (Op::Max, Prop::U64(x)) => Some(Prop::U64(*x)), + (Op::Max, Prop::I32(x)) => Some(Prop::I32(*x)), + (Op::Max, Prop::I64(x)) => Some(Prop::I64(*x)), + (Op::Max, Prop::F32(x)) => { + if x.is_finite() { + Some(Prop::F32(*x)) } else { - out.push(p); + None + } + } + (Op::Max, Prop::F64(x)) => { + if x.is_finite() { + Some(Prop::F64(*x)) + } else { + None } } - out - }; - let per_elem_map = |vals: Vec, op: Op| -> Vec { + // Non-numeric scalars for numeric aggs => None + (Op::Sum, _) | (Op::Avg, _) | (Op::Min, _) | (Op::Max, _) => None, + + // Selectors/qualifiers are handled elsewhere. + _ => None, + } + } + + /// Evaluate ops left-to-right to produce either a reduced scalar, + /// or a sequence plus quantifiers to apply. We also track whether + /// the produced sequence is truly *temporal* (came from a temporal + /// property) or just a normalized wrapper for element-quantifiers. + fn eval_ops(&self, mut state: ValueType) -> (Option, Option>, Vec, bool) { + // Collect quantifiers in source order. + let mut qualifiers: Vec = Vec::new(); + // Whether current sequence represents *time*. + let mut seq_is_temporal = matches!(state, ValueType::Seq(..)); + // Track whether we've seen ANY/ALL before the first aggregator. + let mut seen_qual_before_agg = false; + + // Apply list-style aggregator to a single Prop (list or scalar). + let per_step_map = |vals: Vec, op: Op| -> Vec { vals.into_iter() - .filter_map(|p| match (op, p) { - (Op::Len, Prop::List(inner)) => Some(Prop::U64(inner.len() as u64)), - (Op::Sum, Prop::List(inner)) => { - Self::aggregate_values(inner.as_slice(), Op::Sum) - } - (Op::Avg, Prop::List(inner)) => { - Self::aggregate_values(inner.as_slice(), Op::Avg) - } - (Op::Min, Prop::List(inner)) => { - Self::aggregate_values(inner.as_slice(), Op::Min) + .filter_map(|p| Self::apply_agg_to_prop(&p, op)) + .collect() + }; + + // Collapse the temporal sequence itself (aggregate OVER TIME). + let reduce_over_seq = |vs: Vec, op: Op| -> Option { + match op { + Op::Len => Some(Prop::U64(vs.len() as u64)), // number of time steps + Op::Sum | Op::Avg | Op::Min | Op::Max => { + if vs.is_empty() { + return None; } - (Op::Max, Prop::List(inner)) => { - Self::aggregate_values(inner.as_slice(), Op::Max) + // Only defined for sequences of scalars; if steps hold lists, return None. + if matches!(vs.first(), Some(Prop::List(_))) { + return None; } - _ => None, - }) - .collect() + Self::aggregate_values(&vs, op) + } + _ => None, + } }; - for (i, op) in self.ops.iter().enumerate() { + for op in &self.ops { match *op { Op::First => { state = match state { - ValueType::Seq(vs) => ValueType::Scalar(vs.first().cloned()), + ValueType::Seq(vs) => { + // Collapsing time: after this, no temporal sequence remains. + seq_is_temporal = false; + ValueType::Scalar(vs.first().cloned()) + } ValueType::Scalar(s) => ValueType::Scalar(s), }; } Op::Last => { state = match state { - ValueType::Seq(vs) => ValueType::Scalar(vs.last().cloned()), + ValueType::Seq(vs) => { + seq_is_temporal = false; + ValueType::Scalar(vs.last().cloned()) + } ValueType::Scalar(s) => ValueType::Scalar(s), }; } + Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => { state = match state { + // If a quantifier already occurred, aggregate per time step (do NOT collapse time). + ValueType::Seq(vs) if seen_qual_before_agg => { + ValueType::Seq(per_step_map(vs, *op)) + } + // Otherwise, aggregate ACROSS time to one scalar. ValueType::Seq(vs) => { - if matches!(vs.first(), Some(Prop::List(_))) { - ValueType::Seq(per_elem_map(vs, *op)) - } else { - ValueType::Scalar(Self::aggregate_values(&vs, *op)) - } + seq_is_temporal = false; + ValueType::Scalar(reduce_over_seq(vs, *op)) } + // Non-temporal list -> reduce to scalar. ValueType::Scalar(Some(Prop::List(inner))) => { ValueType::Scalar(Self::aggregate_values(inner.as_slice(), *op)) } - ValueType::Scalar(Some(_)) => ValueType::Scalar(None), + // Non-temporal scalar -> singleton semantics. + ValueType::Scalar(Some(p)) => { + ValueType::Scalar(Self::apply_agg_to_prop(&p, *op)) + } ValueType::Scalar(None) => ValueType::Scalar(None), }; } + Op::Any | Op::All => { - qualifier = Some(*op); - let later = has_later_reduce(&self.ops, i); + qualifiers.push(*op); + seen_qual_before_agg = true; + + // If we *already* have a temporal sequence, keep it (temporal stays true). + // Otherwise, normalize the current (non-temporal) value into a 1-step sequence, + // but mark that sequence as NON-TEMPORAL so we don't treat the first quantifier + // as temporal in apply_eval. state = match state { ValueType::Seq(vs) => { - if later { - ValueType::Seq(vs) - } else { - ValueType::Seq(flatten_one(vs)) - } + // remains temporal + ValueType::Seq(vs) } ValueType::Scalar(Some(Prop::List(inner))) => { - if later { - ValueType::Seq(vec![Prop::List(inner)]) - } else { - ValueType::Seq(inner.as_slice().to_vec()) - } + seq_is_temporal = false; + ValueType::Seq(vec![Prop::List(inner)]) + } + ValueType::Scalar(Some(p)) => { + seq_is_temporal = false; + ValueType::Seq(vec![p]) + } + ValueType::Scalar(None) => { + seq_is_temporal = false; + ValueType::Seq(vec![]) } - ValueType::Scalar(Some(p)) => ValueType::Seq(vec![p]), - ValueType::Scalar(None) => ValueType::Seq(vec![]), }; } } } - if let Some(q) = qualifier { - let elems = match state { - ValueType::Seq(vs) => vs, - ValueType::Scalar(Some(Prop::List(inner))) => inner.as_slice().to_vec(), - ValueType::Scalar(Some(p)) => vec![p], - ValueType::Scalar(None) => vec![], - }; - return (None, Some(elems), Some(q)); - } - match state { - ValueType::Scalar(v) => (v, None, None), - ValueType::Seq(vs) => (None, Some(vs), None), + ValueType::Scalar(v) => (v, None, qualifiers, seq_is_temporal), + ValueType::Seq(vs) => (None, Some(vs), qualifiers, seq_is_temporal), } } @@ -1075,40 +1213,86 @@ impl PropertyFilter { &self, reduced: Option, maybe_seq: Option>, - qualifier: Option, + qualifiers: Vec, + seq_is_temporal: bool, ) -> bool { - // 1) Reduced scalar -> compare directly - // For example: - // 1. NodeFilter::property("temp").temporal().avg().ge(Prop::F64(10.0)) - // 2. NodeFilter::property("readings").temporal().first().len().eq(Prop::U64(3)) - // 3. NodeFilter::property("scores").avg().gt(Prop::F64(0.5)) + // 1) Reduced to a single scalar -> compare directly. if let Some(value) = reduced { return self .operator .apply_to_property(&self.prop_value, Some(&value)); } - // 2) Qualifier over a sequence (ANY/ALL) - // For example: - // 1. NodeFilter::property("tags").any().eq(Prop::Str("gold".into())) - // 2. NodeFilter::property("price").temporal().any().gt(Prop::F64(100.0)) - if let Some(q) = qualifier { - let vals = maybe_seq.unwrap_or_default(); - if vals.is_empty() { + // 2) Quantifiers present + if !qualifiers.is_empty() { + // If the sequence is truly temporal, treat the **first** qualifier as temporal; + // otherwise, treat *all* qualifiers as element-level. + let (temporal_q_opt, elem_quals): (Option, &[Op]) = if seq_is_temporal { + (Some(qualifiers[0]), &qualifiers[1..]) + } else { + (None, &qualifiers[..]) + }; + + let pred = |p: &Prop| self.operator.apply_to_property(&self.prop_value, Some(p)); + + if let Some(seq) = maybe_seq { + // If there's a temporal quantifier, combine across time with it. + if let Some(temporal_q) = temporal_q_opt { + let mut saw_step = false; + match temporal_q { + Op::All => { + for p in &seq { + saw_step = true; + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if !ok { + return false; + } + } + return saw_step; + } + Op::Any => { + for p in &seq { + saw_step = true; + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if ok { + return true; + } + } + return false; + } + _ => unreachable!(), + } + } else { + // No temporal combinator: just apply *element* quantifiers to the single value + // each seq item represents (this seq is non-temporal normalization). + // Since it's a non-temporal normalization, there is exactly one item (by construction), + // but handling multiple is harmless. + for p in &seq { + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if ok { + return true; + } + } + return false; + } + } else { return false; } - let chk = |v: &Prop| self.operator.apply_to_property(&self.prop_value, Some(v)); - return match q { - Op::Any => vals.iter().any(chk), - Op::All => vals.iter().all(chk), - _ => unreachable!(), - }; } - // 3) Compare whole sequence as a List, or missing value - // For example: - // 1. NodeFilter::property("temperature").temporal().eq(Prop::List(vec![...])) - // 2. NodeFilter::property("tags").eq(Prop::List(vec!["gold", "silver"])) + // 3) No quantifiers and not reduced: compare the whole sequence as a list. if let Some(seq) = maybe_seq { let full = Prop::List(Arc::new(seq)); self.operator @@ -1119,20 +1303,15 @@ impl PropertyFilter { } fn eval_scalar_and_apply(&self, prop: Option) -> bool { - let (reduced, maybe_seq, qualifier) = self.eval_ops(ValueType::Scalar(prop)); - self.apply_eval(reduced, maybe_seq, qualifier) + let (reduced, maybe_seq, qualifiers, seq_is_temporal) = + self.eval_ops(ValueType::Scalar(prop)); + self.apply_eval(reduced, maybe_seq, qualifiers, seq_is_temporal) } fn eval_temporal_and_apply(&self, props: Vec) -> bool { - // Special-case: two qualifiers on temporal -> directly compute final bool. - // For example: - // 1. NodeFilter::property("p_flags").temporal().all().all().eq(Prop::u64(1)) - // 2. NodeFilter::property("p_flags").temporal().any().all().eq(Prop::bool(true)) - if self.ops.len() == 2 && self.ops[0].is_qualifier() && self.ops[1].is_qualifier() { - return self.apply_two_qualifiers_temporal(&props, self.ops[0], self.ops[1]); - } - let (reduced, maybe_seq, qualifier) = self.eval_ops(ValueType::Seq(props)); - self.apply_eval(reduced, maybe_seq, qualifier) + let (reduced, maybe_seq, qualifiers, seq_is_temporal) = + self.eval_ops(ValueType::Seq(props)); + self.apply_eval(reduced, maybe_seq, qualifiers, seq_is_temporal) } pub fn matches(&self, other: Option<&Prop>) -> bool { @@ -1307,33 +1486,19 @@ impl InternalPropertyFilterOps for Arc { 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; - fn fuzzy_search( &self, prop_value: impl Into, @@ -1346,63 +1511,49 @@ impl PropertyFilterOps for T { fn eq(&self, value: impl Into) -> PropertyFilter { PropertyFilter::eq(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn ne(&self, value: impl Into) -> PropertyFilter { PropertyFilter::ne(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn le(&self, value: impl Into) -> PropertyFilter { PropertyFilter::le(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn ge(&self, value: impl Into) -> PropertyFilter { PropertyFilter::ge(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn lt(&self, value: impl Into) -> PropertyFilter { PropertyFilter::lt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn gt(&self, value: impl Into) -> PropertyFilter { PropertyFilter::gt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) } - fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { PropertyFilter::is_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) } - fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { PropertyFilter::is_not_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) } - fn is_none(&self) -> PropertyFilter { PropertyFilter::is_none(self.property_ref()).with_ops(self.ops().iter().copied()) } - fn is_some(&self) -> PropertyFilter { PropertyFilter::is_some(self.property_ref()).with_ops(self.ops().iter().copied()) } - fn starts_with(&self, value: impl Into) -> PropertyFilter { PropertyFilter::starts_with(self.property_ref(), value.into()) .with_ops(self.ops().iter().copied()) } - fn ends_with(&self, value: impl Into) -> PropertyFilter { PropertyFilter::ends_with(self.property_ref(), value.into()) .with_ops(self.ops().iter().copied()) } - fn contains(&self, value: impl Into) -> PropertyFilter { PropertyFilter::contains(self.property_ref(), value.into()) .with_ops(self.ops().iter().copied()) } - fn not_contains(&self, value: impl Into) -> PropertyFilter { PropertyFilter::not_contains(self.property_ref(), value.into()) .with_ops(self.ops().iter().copied()) } - fn fuzzy_search( &self, prop_value: impl Into, @@ -1472,35 +1623,27 @@ impl OpChainBuilder { pub fn first(self) -> Self { self.with_op(Op::First) } - pub fn last(self) -> Self { self.with_op(Op::Last) } - pub fn any(self) -> Self { self.with_op(Op::Any) } - pub fn all(self) -> Self { self.with_op(Op::All) } - pub fn len(self) -> Self { self.with_op(Op::Len) } - pub fn sum(self) -> Self { self.with_op(Op::Sum) } - pub fn avg(self) -> Self { self.with_op(Op::Avg) } - pub fn min(self) -> Self { self.with_op(Op::Min) } - pub fn max(self) -> Self { self.with_op(Op::Max) } @@ -1508,11 +1651,9 @@ impl OpChainBuilder { impl InternalPropertyFilterOps for OpChainBuilder { type Marker = M; - fn property_ref(&self) -> PropertyRef { self.prop_ref.clone() } - fn ops(&self) -> &[Op] { &self.ops } @@ -1561,7 +1702,6 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } - fn sum(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1569,7 +1709,6 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } - fn avg(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1577,7 +1716,6 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } - fn min(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1585,7 +1723,6 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } - fn max(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), From 4281d4ed9bd63fb132c6bcf11712c0e7e24a4fc3 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:56:30 +0100 Subject: [PATCH 04/42] fix arbitrary list, fix tests --- python/python/raphtory/__init__.pyi | 56 --- .../python/raphtory/algorithms/__init__.pyi | 3 - .../test_filters/test_node_property_filter.py | 4 +- raphtory/src/db/graph/views/filter/mod.rs | 88 +++- .../views/filter/model/filter_operator.rs | 127 ++++-- .../views/filter/model/property_filter.rs | 424 ++++++++---------- 6 files changed, 361 insertions(+), 341 deletions(-) diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi index 0a17666de6..e7d3659c82 100644 --- a/python/python/raphtory/__init__.pyi +++ b/python/python/raphtory/__init__.pyi @@ -49,7 +49,6 @@ __all__ = [ "IndexSpec", "Prop", "version", - "DiskGraphStorage", "graphql", "algorithms", "graph_loader", @@ -1376,17 +1375,6 @@ class Graph(GraphView): MutableNode: The node object with the specified id, or None if the node does not exist """ - def persist_as_disk_graph(self, graph_dir: str | PathLike) -> DiskGraphStorage: - """ - save graph in disk_graph format and memory map the result - - Arguments: - graph_dir (str | PathLike): folder where the graph will be saved - - Returns: - DiskGraphStorage: the persisted graph storage - """ - def persistent_graph(self) -> PersistentGraph: """ View graph with persistent semantics @@ -1424,17 +1412,6 @@ class Graph(GraphView): bytes: """ - def to_disk_graph(self, graph_dir: str | PathLike) -> Graph: - """ - Persist graph on disk - - Arguments: - graph_dir (str | PathLike): the folder where the graph will be persisted - - Returns: - Graph: a view of the persisted graph - """ - def to_parquet(self, graph_dir: str | PathLike): """ Persist graph to parquet files @@ -2209,7 +2186,6 @@ class PersistentGraph(GraphView): bytes: """ - def to_disk_graph(self, graph_dir): ... def update_metadata(self, metadata: dict) -> None: """ Updates metadata of the graph. @@ -6142,35 +6118,3 @@ class Prop(object): def u8(value): ... def version(): ... - -class DiskGraphStorage(object): - def __repr__(self): - """Return repr(self).""" - - def append_node_temporal_properties(self, location, chunk_size=20000000): ... - def graph_dir(self): ... - @staticmethod - def load_from_dir(graph_dir): ... - @staticmethod - def load_from_pandas(graph_dir, edge_df, time_col, src_col, dst_col): ... - @staticmethod - def load_from_parquets( - graph_dir, - layer_parquet_cols, - node_properties=None, - chunk_size=10000000, - t_props_chunk_size=10000000, - num_threads=4, - node_type_col=None, - node_id_col=None, - ): ... - def load_node_metadata(self, location, col_names=None, chunk_size=None): ... - def load_node_types(self, location, col_name, chunk_size=None): ... - def merge_by_sorted_gids(self, other, graph_dir): - """ - Merge this graph with another `DiskGraph`. Note that both graphs should have nodes that are - sorted by their global ids or the resulting graph will be nonsense! - """ - - def to_events(self): ... - def to_persistent(self): ... diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi index 02ea5eba3e..3873e9b001 100644 --- a/python/python/raphtory/algorithms/__init__.pyi +++ b/python/python/raphtory/algorithms/__init__.pyi @@ -70,7 +70,6 @@ __all__ = [ "max_weight_matching", "Matching", "Infected", - "connected_components", ] def dijkstra_single_source_shortest_paths( @@ -894,5 +893,3 @@ class Infected(object): Returns: int: """ - -def connected_components(graph): ... diff --git a/python/tests/test_base_install/test_filters/test_node_property_filter.py b/python/tests/test_base_install/test_filters/test_node_property_filter.py index 8164e4a309..0275ffc7af 100644 --- a/python/tests/test_base_install/test_filters/test_node_property_filter.py +++ b/python/tests/test_base_install/test_filters/test_node_property_filter.py @@ -174,7 +174,7 @@ def check(graph): filter_expr = filter.Node.property("p20").temporal().all().starts_with("Gold") result_ids = sorted(graph.filter(filter_expr).nodes.id) - expected_ids = ["1", "3", "4"] + expected_ids = ["3", "4"] assert result_ids == expected_ids return check @@ -210,7 +210,7 @@ def check(graph): filter_expr = filter.Node.property("p20").temporal().all().ends_with("ship") result_ids = sorted(graph.filter(filter_expr).nodes.id) - expected_ids = ["1", "2"] + expected_ids = ["2"] assert result_ids == expected_ids filter_expr = filter.Node.metadata("p10").ends_with("ane") diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 67df94252e..8c89360c91 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1379,10 +1379,7 @@ pub(crate) mod test_filters { } } - use crate::db::graph::{ - assertions::GraphTransformer, - views::filter::{internal::CreateFilter, model::TryAsCompositeFilter}, - }; + use crate::db::graph::assertions::GraphTransformer; fn init_nodes_graph< G: StaticGraphViewOps @@ -2786,7 +2783,7 @@ pub(crate) mod test_filters { ); let filter = NodeFilter::property("p1").temporal().all().ne("Gold_ship"); - let expected_results = vec!["1", "2"]; + let expected_results = vec!["1"]; assert_filter_nodes_results( init_nodes_graph, IdentityGraphTransformer, @@ -2894,7 +2891,7 @@ pub(crate) mod test_filters { ); let filter = NodeFilter::property("p2").temporal().all().le(10u64); - let expected_results = vec!["2", "3"]; + let expected_results = vec!["3"]; assert_filter_nodes_results( init_nodes_graph, IdentityGraphTransformer, @@ -3077,7 +3074,7 @@ pub(crate) mod test_filters { let filter = NodeFilter::property("p2") .temporal() - .all() + .any() .is_in(vec![Prop::U64(2)]); let expected_results = vec!["2"]; assert_filter_nodes_results( @@ -4275,7 +4272,10 @@ pub(crate) mod test_filters { }, prelude::{AdditionOps, GraphViewOps, PropertyAdditionOps}, }; - use raphtory_api::core::{entities::properties::prop::Prop, storage::arc_str::ArcStr}; + use raphtory_api::core::{ + entities::properties::prop::{IntoProp, Prop}, + storage::arc_str::ArcStr, + }; use raphtory_storage::mutation::{ addition_ops::InternalAdditionOps, property_addition_ops::InternalPropertyAdditionOps, }; @@ -4314,6 +4314,11 @@ pub(crate) mod test_filters { Prop::List(Arc::new(xs.iter().copied().map(Prop::Bool).collect())) } + #[inline] + fn list(v: Vec) -> Prop { + Prop::List(Arc::new(v)) + } + pub fn init_nodes_graph< G: StaticGraphViewOps + AdditionOps @@ -4342,6 +4347,19 @@ pub(crate) mod test_filters { ("p_i64s", list_i64(&[1, 2, 3])), // min: 1, max: 3, sum: 6, avg: 2.0, len: 3 ("p_f32s", list_f32(&[1.0, 2.0, 3.5])), // min: 1.0, max: 3.5, sum: 6.5, avg: 2.1666666666666665, len: 3 ("p_f64s", list_f64(&[50.0, 40.0])), // min: 40.0, max: 50.0, sum: 90.0, avg: 45.0, len: 2 + ( + "nested_list", + list(vec![ + list(vec![ + list(vec![ + list(vec![50.0.into_prop(), 40.0.into_prop()]), + list(vec![60.0.into_prop()]), + ]), + list(vec![list(vec![46.0.into_prop()])]), + ]), + list(vec![list(vec![list(vec![90.0.into_prop()])])]), + ]), + ), ], ), ( @@ -4393,6 +4411,19 @@ pub(crate) mod test_filters { ("p_i64s", list_i64(&[0, 3, -3])), // min: -3, max: 3, sum: 0, avg: 0.0, len: 3 ("p_f32s", list_f32(&[1.0, 2.5, 3.0])), // min: 1.0, max: 3.0, sum: 6.5, avg: 2.1666666666666665, len: 3 ("p_f64s", list_f64(&[30.0, 60.0])), // min: 30.0, max: 60.0, sum: 90.0, avg: 45.0, len: 2 + ( + "nested_list", + list(vec![ + list(vec![ + list(vec![ + list(vec![50.0.into_prop(), 40.0.into_prop()]), + list(vec![60.0.into_prop()]), + ]), + list(vec![list(vec![46.0.into_prop()])]), + ]), + list(vec![list(vec![list(vec![90.0.into_prop()])])]), + ]), + ), ], ), ( @@ -4409,6 +4440,19 @@ pub(crate) mod test_filters { ("p_i64s", list_i64(&[1, 2, -3])), // min: -3, max: 2, sum: 0, avg: 0.0, len: 3 ("p_f32s", list_f32(&[1.0, 2.0, 3.5])), // min: 1.0, max: 3.5, sum: 6.5, avg: 2.1666666666666665, len: 3 ("p_f64s", list_f64(&[50.0, 40.0])), // min: 40.0, max: 50.0, sum: 90.0, avg: 45.0, len: 2 + ( + "nested_list", + list(vec![ + list(vec![ + list(vec![ + list(vec![50.0.into_prop(), 40.0.into_prop()]), + list(vec![60.0.into_prop()]), + ]), + list(vec![list(vec![46.0.into_prop()])]), + ]), + list(vec![list(vec![list(vec![90.0.into_prop()])])]), + ]), + ), ], ), ( @@ -7469,6 +7513,34 @@ pub(crate) mod test_filters { apply_assertion(filter, &expected); } + #[test] + fn test_node_nested_list_property_all_all_all_any() { + let filter = NodeFilter::property("nested_list") + .all() + .all() + .all() + .any() + .gt(45.0); + + let expected = vec!["n1", "n3"]; + apply_assertion(filter, &expected); + } + + #[test] + fn test_node_nested_list_temporal_property_all_all_all_all_any() { + let filter = NodeFilter::property("nested_list") + .temporal() + .all() + .all() + .all() + .all() + .any() + .gt(45.0); + + let expected = vec!["n3"]; + apply_assertion(filter, &expected); + } + // ------ Temporal All: any ------ #[test] fn test_node_temporal_property_all_any() { diff --git a/raphtory/src/db/graph/views/filter/model/filter_operator.rs b/raphtory/src/db/graph/views/filter/model/filter_operator.rs index 504d3e7392..eb77a11999 100644 --- a/raphtory/src/db/graph/views/filter/model/filter_operator.rs +++ b/raphtory/src/db/graph/views/filter/model/filter_operator.rs @@ -113,55 +113,98 @@ impl FilterOperator { } pub fn apply_to_property(&self, left: &PropertyFilterValue, right: Option<&Prop>) -> bool { + use std::cmp::Ordering::*; + use FilterOperator::*; + use PropertyFilterValue::*; + + let cmp = |op: &FilterOperator, r: &Prop, l: &Prop| -> bool { + match op { + Eq => r == l, + Ne => r != l, + Lt => r.partial_cmp(l).map(|o| o == Less).unwrap_or(false), + Le => r.partial_cmp(l).map(|o| o != Greater).unwrap_or(false), + Gt => r.partial_cmp(l).map(|o| o == Greater).unwrap_or(false), + Ge => r.partial_cmp(l).map(|o| o != Less).unwrap_or(false), + _ => false, + } + }; + match left { - PropertyFilterValue::None => match self { - FilterOperator::IsSome => right.is_some(), - FilterOperator::IsNone => right.is_none(), - _ => unreachable!(), + None => match self { + IsSome => right.is_some(), + IsNone => right.is_none(), + _ => false, // Missing RHS never matches for other ops }, - PropertyFilterValue::Single(l) => match self { - FilterOperator::Eq - | FilterOperator::Ne - | FilterOperator::Lt - | FilterOperator::Le - | FilterOperator::Gt - | FilterOperator::Ge => right.is_some_and(|r| { - // println!("right: {:?}, left: {:?}", r, l); - self.operation()(r, l) - }), - FilterOperator::StartsWith => right.is_some_and(|r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => r.deref().starts_with(l.deref()), - _ => unreachable!(), - }), - FilterOperator::EndsWith => right.is_some_and(|r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => r.deref().ends_with(l.deref()), - _ => unreachable!(), - }), - FilterOperator::Contains => right.is_some_and(|r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => r.deref().contains(l.deref()), - _ => unreachable!(), - }), - FilterOperator::NotContains => right.is_some_and(|r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => !r.deref().contains(l.deref()), - _ => unreachable!(), - }), - FilterOperator::FuzzySearch { + + Single(lv) => match self { + Eq | Ne | Lt | Le | Gt | Ge => { + if let Some(r) = right { + cmp(self, r, lv) + } else { + false + } + } + + StartsWith => { + if let (Some(Prop::Str(rs)), Prop::Str(ls)) = (right, lv) { + rs.deref().starts_with(ls.deref()) + } else { + false + } + } + EndsWith => { + if let (Some(Prop::Str(rs)), Prop::Str(ls)) = (right, lv) { + rs.deref().ends_with(ls.deref()) + } else { + false + } + } + Contains => { + if let (Some(Prop::Str(rs)), Prop::Str(ls)) = (right, lv) { + rs.deref().contains(ls.deref()) + } else { + false + } + } + NotContains => { + if let (Some(Prop::Str(rs)), Prop::Str(ls)) = (right, lv) { + !rs.deref().contains(ls.deref()) + } else { + false + } + } + + FuzzySearch { levenshtein_distance, prefix_match, - } => right.is_some_and(|r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => { - let fuzzy_fn = self.fuzzy_search(*levenshtein_distance, *prefix_match); - fuzzy_fn(l, r) + } => { + if let (Some(Prop::Str(rs)), Prop::Str(ls)) = (right, lv) { + let f = self.fuzzy_search(*levenshtein_distance, *prefix_match); + f(ls, rs) + } else { + false } - _ => unreachable!(), - }), - _ => unreachable!(), + } + + In | NotIn | IsSome | IsNone => false, }, - PropertyFilterValue::Set(l) => match self { - FilterOperator::In | FilterOperator::NotIn => { - right.is_some_and(|r| self.collection_operation()(l, r)) + + Set(set) => match self { + In => { + if let Some(r) = right { + set.contains(r) + } else { + false + } } - _ => unreachable!(), + NotIn => { + if let Some(r) = right { + !set.contains(r) + } else { + false + } + } + _ => false, }, } } 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 dc94510613..277ca7cc15 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -2,7 +2,11 @@ use crate::{ db::{ api::{ properties::{internal::InternalPropertiesOps, Metadata, Properties}, - view::{internal::GraphView, node::NodeViewOps, EdgeViewOps}, + view::{ + internal::{GraphView, NodeTimeSemanticsOps}, + node::NodeViewOps, + EdgeViewOps, + }, }, graph::{ edge::EdgeView, @@ -19,7 +23,7 @@ use crate::{ }, }, errors::GraphError, - prelude::{GraphViewOps, PropertiesOps}, + prelude::{GraphViewOps, PropertiesOps, TimeOps}, }; use itertools::Itertools; use raphtory_api::core::{ @@ -74,26 +78,10 @@ enum Shape { Scalar(PropType), List(Box), Seq(Box), - /// A pending quantifier (any/all) over elements of the inner shape. - /// We keep the element dtype available so the final operator can be - /// validated against element type (predicate-after-quantifier). Quantified(Box, Op), } -impl Shape { - #[inline] - fn elem_dtype(&self) -> Option { - match self { - Shape::Scalar(t) => Some(t.clone()), - Shape::List(t) => Some(*t.clone()), - Shape::Seq(inner) => inner.elem_dtype(), - Shape::Quantified(inner, _) => inner.elem_dtype(), - } - } -} - -/// Count consecutive Quantified wrappers and return (base shape, depth). -fn flatten_quantified_depth<'a>(mut s: &'a Shape) -> (&'a Shape, usize) { +fn flatten_quantified_depth(mut s: &Shape) -> (&Shape, usize) { let mut depth = 0; while let Shape::Quantified(inner, _) = s { depth += 1; @@ -102,19 +90,6 @@ fn flatten_quantified_depth<'a>(mut s: &'a Shape) -> (&'a Shape, usize) { (s, depth) } -/// Unwrap `n` list layers from a PropType::List nesting. -fn unwrap_list_n(mut t: PropType, mut n: usize) -> Option { - while n > 0 { - if let PropType::List(inner) = t { - t = *inner; - n -= 1; - } else { - return None; - } - } - Some(t) -} - #[inline] fn agg_result_dtype(inner: &PropType, op: Op, ctx: &str) -> Result { use PropType::*; @@ -406,6 +381,11 @@ impl PropertyFilter { } } + #[inline] + fn has_aggregator(&self) -> bool { + self.ops.iter().copied().any(Op::is_aggregator) + } + fn validate_single_dtype( &self, expected: &PropType, @@ -433,12 +413,38 @@ impl PropertyFilter { Ok(filter_dtype) } + #[inline] + fn has_elem_qualifier(&self) -> bool { + self.ops.iter().any(|op| matches!(op, Op::Any | Op::All)) + } + + #[inline] + fn is_temporal_ref(&self) -> bool { + matches!(self.prop_ref, PropertyRef::TemporalProperty(_)) + } + + #[inline] + fn has_temporal_first_qualifier(&self) -> bool { + self.is_temporal_ref() && matches!(self.ops.first(), Some(Op::Any | Op::All)) + } + fn validate_operator_against_dtype( &self, dtype: &PropType, expect_map: bool, ) -> Result<(), GraphError> { - if self.ops.iter().copied().any(Op::is_aggregator) { + if matches!( + self.operator, + FilterOperator::IsSome | FilterOperator::IsNone + ) { + if self.has_elem_qualifier() && !self.has_temporal_first_qualifier() { + return Err(GraphError::InvalidFilter( + "Invalid filter: Operator IS_SOME/IS_NONE is not supported with element qualifiers; apply it to the list itself (without elem qualifiers).".into() + )); + } + } + + if self.has_aggregator() { match self.operator { FilterOperator::StartsWith | FilterOperator::EndsWith @@ -457,22 +463,22 @@ impl PropertyFilter { match self.operator { FilterOperator::Eq | FilterOperator::Ne => { - self.validate_single_dtype(dtype, expect_map)?; + let _ = self.validate_single_dtype(dtype, expect_map)?; } FilterOperator::Lt | FilterOperator::Le | FilterOperator::Gt | FilterOperator::Ge => { - let filter_dtype = self.validate_single_dtype(dtype, expect_map)?; - if !filter_dtype.has_cmp() { - return Err(GraphError::InvalidFilterCmp(filter_dtype)); + let fd = self.validate_single_dtype(dtype, expect_map)?; + if !fd.has_cmp() { + return Err(GraphError::InvalidFilterCmp(fd)); } } FilterOperator::In | FilterOperator::NotIn => match &self.prop_value { + PropertyFilterValue::Set(_) => {} PropertyFilterValue::None => { return Err(GraphError::InvalidFilterExpectSetGotNone(self.operator)) } PropertyFilterValue::Single(_) => { return Err(GraphError::InvalidFilterExpectSetGotSingle(self.operator)) } - PropertyFilterValue::Set(_) => {} }, FilterOperator::IsSome | FilterOperator::IsNone => {} FilterOperator::StartsWith @@ -480,27 +486,41 @@ impl PropertyFilter { | FilterOperator::Contains | FilterOperator::NotContains | FilterOperator::FuzzySearch { .. } => match &self.prop_value { + PropertyFilterValue::Single(v) + if matches!(dtype, PropType::Str) && matches!(v.dtype(), PropType::Str) => {} PropertyFilterValue::None => { return Err(GraphError::InvalidFilterExpectSingleGotNone(self.operator)) } - PropertyFilterValue::Single(v) => { - if !matches!(dtype, PropType::Str) || !matches!(v.dtype(), PropType::Str) { - return Err(GraphError::InvalidContains(self.operator)); - } - } PropertyFilterValue::Set(_) => { return Err(GraphError::InvalidFilterExpectSingleGotSet(self.operator)) } + _ => return Err(GraphError::InvalidContains(self.operator)), }, } Ok(()) } - /// Validate the op chain via a **right-to-left** fold over a Shape model and - /// return the effective dtype against which the final operator is checked. - /// Selectors (`first/last`) are applied *before* any other ops regardless of - /// source order to ensure chains like `.temporal().first().avg()` work as intended. + // helper used at the bottom; include if you don't already have it + #[inline] + fn peel_list_n(mut t: PropType, mut n: usize) -> Result { + while n > 0 { + match t { + PropType::List(inner) => { + t = *inner; + n -= 1; + } + other => { + return Err(GraphError::InvalidFilter(format!( + "Too many any/all quantifiers for list depth (stopped at {:?})", + other + ))) + } + } + } + Ok(t) + } + fn validate_chain_and_infer_effective_dtype( &self, src_dtype: &PropType, @@ -508,7 +528,6 @@ impl PropertyFilter { ) -> Result { use Shape::*; - // Build the base shape from the underlying property type. let base_shape: Shape = if is_temporal { let inner: Shape = match src_dtype { PropType::List(inner) => List(inner.clone()), @@ -522,15 +541,6 @@ impl PropertyFilter { } }; - #[inline] - fn agg_out(inner: &PropType, op: Op, ctx: &str) -> Result { - agg_result_dtype(inner, op, ctx) - } - - // ---- IMPORTANT: selector precedence -------------------------------------- - // To preserve RTL validation but apply selectors first, we reorder ops so that - // selectors come *last* in the vector (closest to the source), which means they - // are processed *first* when we iterate in reverse. let mut selectors: Vec = Vec::new(); let mut others: Vec = Vec::new(); for &op in &self.ops { @@ -543,13 +553,10 @@ impl PropertyFilter { let mut ops_for_validation: Vec = Vec::with_capacity(self.ops.len()); ops_for_validation.extend(others); ops_for_validation.extend(selectors); - // --------------------------------------------------------------------------- - // Right-to-left fold over ops_for_validation. let mut shape = base_shape; for &op in ops_for_validation.iter().rev() { shape = match op { - // --- Selectors collapse one temporal layer --- Op::First | Op::Last => match shape { Seq(inner) => *inner, other => { @@ -560,11 +567,14 @@ impl PropertyFilter { } }, - // --- Quantifiers wrap current list level (or per-time list). - // Allow singleton policy for scalars. Op::Any | Op::All => match shape { + // nesting quantifiers is allowed + Quantified(_, _) => Quantified(Box::new(shape), op), + + // element-level quantifier over list List(_) => Quantified(Box::new(shape), op), + // **temporal** quantifier: do NOT consume a list level here Seq(inner) => match *inner { List(_) | Quantified(_, _) => { Seq(Box::new(Quantified(Box::new(*inner), op))) @@ -579,42 +589,30 @@ impl PropertyFilter { }, Scalar(t) => Quantified(Box::new(Scalar(t)), op), - - _ => { - return Err(GraphError::InvalidFilter(format!( - "{:?} requires a list (or temporal list); got {:?}", - op, shape - ))) - } }, - // --- Aggregators map list/temporal(list|scalar) to a scalar dtype --- Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => match shape { - // Aggregate a plain list List(inner) => { - let t = *inner; - let out = agg_out(&t, op, "over list")?; + let out = agg_result_dtype(&*inner, op, "over list")?; Scalar(out) } - - // Aggregate over time Seq(inner) => match *inner { Scalar(t) => { - let out = agg_out(&t, op, "over time")?; + let out = agg_result_dtype(&t, op, "over time")?; Scalar(out) } List(t) => { - let elem_t = *t; - let out = agg_out(&elem_t, op, "over time of lists")?; + let out = agg_result_dtype(&*t, op, "over time of lists")?; Scalar(out) } - // time series with quantifier on the payload: preserve the quantifier, - // map the inner list dtype through the aggregator. - Quantified(qinner, qop) => { - let mapped_inner = match *qinner { + Quantified(q_inner, qop) => { + let mapped_inner = match *q_inner { List(t) => { - let out = - agg_out(&*t, op, "under temporal quantifier over list")?; + let out = agg_result_dtype( + &*t, + op, + "under temporal quantifier over list", + )?; Scalar(out) } Scalar(t) => { @@ -629,12 +627,10 @@ impl PropertyFilter { } Seq(_) => unreachable!(), }, - - // Aggregator under a non-temporal quantifier: preserve the quantifier. - Quantified(qinner, qop) => { - let mapped_inner = match *qinner { + Quantified(q_inner, qop) => { + let mapped_inner = match *q_inner { List(t) => { - let out = agg_out(&*t, op, "under quantifier over list")?; + let out = agg_result_dtype(&*t, op, "under quantifier over list")?; Scalar(out) } Scalar(t) => { @@ -647,8 +643,6 @@ impl PropertyFilter { }; Quantified(Box::new(mapped_inner), qop) } - - // Aggregators require a collection or temporal sequence before them. Scalar(t) => { return Err(GraphError::InvalidFilter(format!( "{:?} requires list or temporal sequence, got Scalar({:?})", @@ -659,18 +653,15 @@ impl PropertyFilter { }; } - // Effective dtype seen by the final operator. - let effective_dtype: PropType = match shape { + let eff = match shape { Scalar(t) => t, List(t) => PropType::List(t), - // Non-temporal quantified input: - // Any depth of quantifiers over a list exposes the element dtype. - // Any depth over a scalar (singleton policy) exposes the scalar dtype. + // pure (non-temporal) quantifier(s): consume list depth Quantified(_, _) => { - let (base, _qdepth) = flatten_quantified_depth(&shape); + let (base, qdepth) = flatten_quantified_depth(&shape); match base { - List(t) => (**t).clone(), + List(t) => Self::peel_list_n(PropType::List(t.clone()), qdepth)?, Scalar(t) => t.clone(), _ => { return Err(GraphError::InvalidFilter( @@ -680,18 +671,15 @@ impl PropertyFilter { } } - // Temporal payload + // temporal case: the first quantifier is temporal → ignore one quantifier for depth Seq(inner) => match *inner { - // time series of scalars -> List effective dtype Scalar(t) => PropType::List(Box::new(t)), - // time series of lists -> List> effective dtype List(t) => PropType::List(Box::new(PropType::List(t))), - - // Quantified per-time payload: same expose-as-element/singleton semantics. Quantified(_, _) => { - let (base, _qdepth) = flatten_quantified_depth(&*inner); + let (base, qdepth_total) = flatten_quantified_depth(&*inner); + let qdepth_lists = qdepth_total.saturating_sub(1); // skip the temporal one match base { - List(t) => (**t).clone(), + List(t) => Self::peel_list_n(PropType::List(t.clone()), qdepth_lists)?, Scalar(t) => t.clone(), _ => { return Err(GraphError::InvalidFilter( @@ -705,10 +693,7 @@ impl PropertyFilter { }, }; - // NOTE: Do **not** validate the final operator here. - // The caller (resolve_prop_id) will validate using the correct `expect_map_now` - // to support metadata map semantics. - Ok(effective_dtype) + Ok(eff) } pub fn resolve_prop_id(&self, meta: &Meta, expect_map: bool) -> Result { @@ -729,7 +714,6 @@ impl PropertyFilter { Some((id, dtype)) => (id, dtype), }; - // Decide map-semantics for metadata let is_original_map = matches!(original_dtype, PropType::Map(..)); let rhs_is_map = matches!(self.prop_value, PropertyFilterValue::Single(Prop::Map(_))); let expect_map_now = if is_static && rhs_is_map { @@ -738,11 +722,10 @@ impl PropertyFilter { is_static && expect_map && is_original_map }; - // Validate chain and final operator with correct semantics let eff = self.validate_chain_and_infer_effective_dtype(&original_dtype, is_temporal)?; - // (Redundant safety) final check self.validate_operator_against_dtype(&eff, expect_map_now)?; + Ok(id) } @@ -942,8 +925,6 @@ impl PropertyFilter { } } - /// Recursive reducer for nested quantifiers: applies the final predicate - /// at the innermost level and bubbles results back using each quantifier. fn reduce_qualifiers_rec( &self, quals: &[Op], @@ -955,7 +936,6 @@ impl PropertyFilter { } let (q, rest) = (quals[0], &quals[1..]); - // If we have a list, descend normally. if let Prop::List(inner) = v { let elems = inner.as_slice(); let check = |e: &Prop| { @@ -972,31 +952,23 @@ impl PropertyFilter { }; } - // No list at this level: apply singleton semantics. - // Consuming ANY/ALL over a single value is equivalent to just - // continuing with the remaining quantifiers on the same value. if rest.is_empty() { - // Last quantifier => apply to the single value. return match q { Op::Any | Op::All => predicate(v), _ => unreachable!(), }; } - // Still have deeper quantifiers: keep consuming them on the same value. self.reduce_qualifiers_rec(rest, v, predicate) } - /// Apply a list-style aggregator to a single Prop, with singleton semantics for scalars. fn apply_agg_to_prop(p: &Prop, op: Op) -> Option { match (op, p) { - // ----- Lists ----- (Op::Len, Prop::List(inner)) => Some(Prop::U64(inner.len() as u64)), (Op::Sum, Prop::List(inner)) | (Op::Avg, Prop::List(inner)) | (Op::Min, Prop::List(inner)) | (Op::Max, Prop::List(inner)) => Self::aggregate_values(inner.as_slice(), op), - // ----- Scalars (singleton semantics) ----- (Op::Len, _) => Some(Prop::U64(1)), (Op::Sum, Prop::U8(x)) => Some(Prop::U8(*x)), @@ -1083,43 +1055,29 @@ impl PropertyFilter { } } - // Non-numeric scalars for numeric aggs => None (Op::Sum, _) | (Op::Avg, _) | (Op::Min, _) | (Op::Max, _) => None, - // Selectors/qualifiers are handled elsewhere. _ => None, } } - /// Evaluate ops left-to-right to produce either a reduced scalar, - /// or a sequence plus quantifiers to apply. We also track whether - /// the produced sequence is truly *temporal* (came from a temporal - /// property) or just a normalized wrapper for element-quantifiers. fn eval_ops(&self, mut state: ValueType) -> (Option, Option>, Vec, bool) { - // Collect quantifiers in source order. let mut qualifiers: Vec = Vec::new(); - // Whether current sequence represents *time*. let mut seq_is_temporal = matches!(state, ValueType::Seq(..)); - // Track whether we've seen ANY/ALL before the first aggregator. let mut seen_qual_before_agg = false; - // Apply list-style aggregator to a single Prop (list or scalar). let per_step_map = |vals: Vec, op: Op| -> Vec { vals.into_iter() .filter_map(|p| Self::apply_agg_to_prop(&p, op)) .collect() }; - // Collapse the temporal sequence itself (aggregate OVER TIME). + // Aggregate OVER TIME (when no prior qualifier). let reduce_over_seq = |vs: Vec, op: Op| -> Option { match op { - Op::Len => Some(Prop::U64(vs.len() as u64)), // number of time steps + Op::Len => Some(Prop::U64(vs.len() as u64)), Op::Sum | Op::Avg | Op::Min | Op::Max => { - if vs.is_empty() { - return None; - } - // Only defined for sequences of scalars; if steps hold lists, return None. - if matches!(vs.first(), Some(Prop::List(_))) { + if vs.is_empty() || matches!(vs.first(), Some(Prop::List(_))) { return None; } Self::aggregate_values(&vs, op) @@ -1130,42 +1088,33 @@ impl PropertyFilter { for op in &self.ops { match *op { - Op::First => { - state = match state { - ValueType::Seq(vs) => { - // Collapsing time: after this, no temporal sequence remains. - seq_is_temporal = false; - ValueType::Scalar(vs.first().cloned()) - } - ValueType::Scalar(s) => ValueType::Scalar(s), - }; - } - Op::Last => { + Op::First | Op::Last => { state = match state { ValueType::Seq(vs) => { seq_is_temporal = false; - ValueType::Scalar(vs.last().cloned()) + let v = if matches!(*op, Op::First) { + vs.first() + } else { + vs.last() + }; + ValueType::Scalar(v.cloned()) } - ValueType::Scalar(s) => ValueType::Scalar(s), + s @ ValueType::Scalar(_) => s, }; } Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => { state = match state { - // If a quantifier already occurred, aggregate per time step (do NOT collapse time). ValueType::Seq(vs) if seen_qual_before_agg => { ValueType::Seq(per_step_map(vs, *op)) } - // Otherwise, aggregate ACROSS time to one scalar. ValueType::Seq(vs) => { seq_is_temporal = false; ValueType::Scalar(reduce_over_seq(vs, *op)) } - // Non-temporal list -> reduce to scalar. ValueType::Scalar(Some(Prop::List(inner))) => { ValueType::Scalar(Self::aggregate_values(inner.as_slice(), *op)) } - // Non-temporal scalar -> singleton semantics. ValueType::Scalar(Some(p)) => { ValueType::Scalar(Self::apply_agg_to_prop(&p, *op)) } @@ -1176,16 +1125,8 @@ impl PropertyFilter { Op::Any | Op::All => { qualifiers.push(*op); seen_qual_before_agg = true; - - // If we *already* have a temporal sequence, keep it (temporal stays true). - // Otherwise, normalize the current (non-temporal) value into a 1-step sequence, - // but mark that sequence as NON-TEMPORAL so we don't treat the first quantifier - // as temporal in apply_eval. state = match state { - ValueType::Seq(vs) => { - // remains temporal - ValueType::Seq(vs) - } + ValueType::Seq(vs) => ValueType::Seq(vs), // still temporal ValueType::Scalar(Some(Prop::List(inner))) => { seq_is_temporal = false; ValueType::Seq(vec![Prop::List(inner)]) @@ -1216,83 +1157,69 @@ impl PropertyFilter { qualifiers: Vec, seq_is_temporal: bool, ) -> bool { - // 1) Reduced to a single scalar -> compare directly. if let Some(value) = reduced { return self .operator .apply_to_property(&self.prop_value, Some(&value)); } - // 2) Quantifiers present if !qualifiers.is_empty() { - // If the sequence is truly temporal, treat the **first** qualifier as temporal; - // otherwise, treat *all* qualifiers as element-level. - let (temporal_q_opt, elem_quals): (Option, &[Op]) = if seq_is_temporal { + let (temporal_q_opt, elem_quals) = if seq_is_temporal { (Some(qualifiers[0]), &qualifiers[1..]) } else { (None, &qualifiers[..]) }; - let pred = |p: &Prop| self.operator.apply_to_property(&self.prop_value, Some(p)); - - if let Some(seq) = maybe_seq { - // If there's a temporal quantifier, combine across time with it. - if let Some(temporal_q) = temporal_q_opt { - let mut saw_step = false; - match temporal_q { - Op::All => { - for p in &seq { - saw_step = true; - let ok = if elem_quals.is_empty() { - pred(p) - } else { - self.reduce_qualifiers_rec(elem_quals, p, &pred) - }; - if !ok { - return false; - } - } - return saw_step; - } - Op::Any => { - for p in &seq { - saw_step = true; - let ok = if elem_quals.is_empty() { - pred(p) - } else { - self.reduce_qualifiers_rec(elem_quals, p, &pred) - }; - if ok { - return true; - } + let Some(seq) = maybe_seq else { return false }; + + if let Some(tq) = temporal_q_opt { + let mut saw = false; + match tq { + Op::All => { + for p in &seq { + saw = true; + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if !ok { + return false; } - return false; } - _ => unreachable!(), + return saw; } - } else { - // No temporal combinator: just apply *element* quantifiers to the single value - // each seq item represents (this seq is non-temporal normalization). - // Since it's a non-temporal normalization, there is exactly one item (by construction), - // but handling multiple is harmless. - for p in &seq { - let ok = if elem_quals.is_empty() { - pred(p) - } else { - self.reduce_qualifiers_rec(elem_quals, p, &pred) - }; - if ok { - return true; + Op::Any => { + for p in &seq { + saw = true; + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if ok { + return true; + } } + return false; } - return false; + _ => unreachable!(), } } else { + for p in &seq { + let ok = if elem_quals.is_empty() { + pred(p) + } else { + self.reduce_qualifiers_rec(elem_quals, p, &pred) + }; + if ok { + return true; + } + } return false; } } - // 3) No quantifiers and not reduced: compare the whole sequence as a list. if let Some(seq) = maybe_seq { let full = Prop::List(Arc::new(seq)); self.operator @@ -1303,15 +1230,13 @@ impl PropertyFilter { } fn eval_scalar_and_apply(&self, prop: Option) -> bool { - let (reduced, maybe_seq, qualifiers, seq_is_temporal) = - self.eval_ops(ValueType::Scalar(prop)); - self.apply_eval(reduced, maybe_seq, qualifiers, seq_is_temporal) + let (r, s, q, is_t) = self.eval_ops(ValueType::Scalar(prop)); + self.apply_eval(r, s, q, is_t) } fn eval_temporal_and_apply(&self, props: Vec) -> bool { - let (reduced, maybe_seq, qualifiers, seq_is_temporal) = - self.eval_ops(ValueType::Seq(props)); - self.apply_eval(reduced, maybe_seq, qualifiers, seq_is_temporal) + let (r, s, q, is_t) = self.eval_ops(ValueType::Seq(props)); + self.apply_eval(r, s, q, is_t) } pub fn matches(&self, other: Option<&Prop>) -> bool { @@ -1362,16 +1287,55 @@ impl PropertyFilter { prop_id: usize, node: NodeStorageRef, ) -> bool { - let node = NodeView::new_internal(graph, node.vid()); + let node_view = NodeView::new_internal(graph, node.vid()); + match self.prop_ref { PropertyRef::Metadata(_) => { - let props = node.metadata(); + let props = node_view.metadata(); self.is_metadata_matched(prop_id, props) } - PropertyRef::TemporalProperty(_) | PropertyRef::Property(_) => { - let props = node.properties(); + + PropertyRef::Property(_) => { + let props = node_view.properties(); self.is_property_matched(prop_id, props) } + + PropertyRef::TemporalProperty(_) => { + let props = node_view.properties(); + let Some(tview) = props.temporal().get_by_id(prop_id) else { + return false; // no temporal values at all for this node/property + }; + + let seq: Vec = tview.values().collect(); + + if self.ops.is_empty() { + return self.eval_temporal_and_apply(seq); + } + + // If the FIRST quantifier is a temporal quantifier and it's ALL, + // require the temporal property to be present on EVERY node update time. + if self + .ops + .iter() + .copied() + .find(|op| matches!(op, Op::Any | Op::All)) + == Some(Op::All) + { + let semantics = graph.node_time_semantics(); + let core_node = graph.core_node(node_view.node); + + let node_update_count = + semantics.node_updates(core_node.as_ref(), graph).count(); + let prop_time_count = seq.len(); + + // Missing at any timepoint? Leading temporal ALL must fail. + if prop_time_count < node_update_count { + return false; + } + } + + self.eval_temporal_and_apply(seq) + } } } From fdce92682021a0e9b0b54909d9955abb0b3d38d4 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:31:57 +0100 Subject: [PATCH 05/42] merge python traits, fix tests, add more validations --- python/python/raphtory/filter/__init__.pyi | 15 +- .../test_filters/test_node_property_filter.py | 28 +- .../views/filter/model/property_filter.rs | 73 +- raphtory/src/python/filter/filter_expr.rs | 27 +- .../python/filter/property_filter_builders.rs | 766 +++++++++++------- raphtory/src/python/graph/node.rs | 2 +- raphtory/src/python/graph/views/graph_view.rs | 2 +- .../types/macros/trait_impl/filter_ops.rs | 29 +- 8 files changed, 605 insertions(+), 337 deletions(-) diff --git a/python/python/raphtory/filter/__init__.pyi b/python/python/raphtory/filter/__init__.pyi index 94427efba7..009c7c5670 100644 --- a/python/python/raphtory/filter/__init__.pyi +++ b/python/python/raphtory/filter/__init__.pyi @@ -169,19 +169,6 @@ class ExplodedEdge(object): def property(name): ... class Property(PropertyFilterOps): - """ - Construct a property filter - - Arguments: - name (str): the name of the property to filter - """ - def temporal(self): ... -class Metadata(PropertyFilterOps): - """ - Construct a metadata filter - - Arguments: - name (str): the name of the property to filter - """ +class Metadata(PropertyFilterOps): ... diff --git a/python/tests/test_base_install/test_filters/test_node_property_filter.py b/python/tests/test_base_install/test_filters/test_node_property_filter.py index 0275ffc7af..37527ad0d7 100644 --- a/python/tests/test_base_install/test_filters/test_node_property_filter.py +++ b/python/tests/test_base_install/test_filters/test_node_property_filter.py @@ -810,18 +810,24 @@ def check(graph): return check +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) def test_filter_nodes_with_with_qualifier_alongside_illegal_agg_operators(): - with pytest.raises( - Exception, - match=r"List aggregation len cannot be used after an element qualifier \(any/all\)", - ): - filter.Node.property("prop8").all().len() - - with pytest.raises( - Exception, - match=r"Element qualifiers \(any/all\) cannot be used after a list aggregation \(len/sum/avg/min/max\).", - ): - filter.Node.property("prop8").sum().any() + def check(graph): + filter_expr = filter.Node.property("prop8").all().len() + with pytest.raises( + Exception, + match=r"List aggregation len cannot be used after an element qualifier \(any/all\)", + ): + graph.filter(filter_expr).nodes.id + + filter_expr = filter.Node.property("prop8").sum().any() + with pytest.raises( + Exception, + match=r"Element qualifiers \(any/all\) cannot be used after a list aggregation \(len/sum/avg/min/max\).", + ): + graph.filter(filter_expr).nodes.id + + return check @with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) 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 277ca7cc15..bab1ed6aaa 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -528,6 +528,70 @@ impl PropertyFilter { ) -> Result { use Shape::*; + // Ordering guard for qualifiers/aggregators/selectors === + // Disallow: + // 1) aggregator after qualifier, unless the *first* qualifier is temporal + // 2) qualifier after aggregator (always illegal) + // + // Ensures: + // - property(...).all().len() -> error + // - property(...).sum().any() -> error + // - property(...).temporal().all().len() -> OK + // - property(...).temporal().first().all().len() -> error + let mut saw_agg = false; + let mut saw_qual = false; + let mut saw_selector_before_first_qual = false; + let mut first_qual_is_temporal = false; + + #[inline] + fn agg_name(op: Op) -> &'static str { + match op { + Op::Len => "len", + Op::Sum => "sum", + Op::Avg => "avg", + Op::Min => "min", + Op::Max => "max", + _ => unreachable!(), + } + } + + for &op in &self.ops { + match op { + Op::First | Op::Last => { + if !saw_qual { + // A selector before the first qualifier collapses the temporal sequence, + // so the upcoming first qualifier cannot be considered temporal. + saw_selector_before_first_qual = true; + } + } + Op::Any | Op::All => { + // Qualifier after any aggregation is always illegal. + if saw_agg { + return Err(GraphError::InvalidFilter( + "Element qualifiers (any/all) cannot be used after a list aggregation (len/sum/avg/min/max).".into() + )); + } + // Record once whether the very first qualifier is "temporal-first" + // (chain is temporal and no selector has run yet). + if !saw_qual && is_temporal && !saw_selector_before_first_qual { + first_qual_is_temporal = true; + } + saw_qual = true; + } + Op::Len | Op::Sum | Op::Avg | Op::Min | Op::Max => { + // Aggregator after a qualifier is illegal unless that first qualifier is temporal. + if saw_qual && !first_qual_is_temporal { + return Err(GraphError::InvalidFilter(format!( + "List aggregation {} cannot be used after an element qualifier (any/all)", + agg_name(op) + ))); + } + saw_agg = true; + } + } + } + + // Build base shape let base_shape: Shape = if is_temporal { let inner: Shape = match src_dtype { PropType::List(inner) => List(inner.clone()), @@ -541,6 +605,7 @@ impl PropertyFilter { } }; + // Defer selectors to the end of validation let mut selectors: Vec = Vec::new(); let mut others: Vec = Vec::new(); for &op in &self.ops { @@ -554,6 +619,7 @@ impl PropertyFilter { ops_for_validation.extend(others); ops_for_validation.extend(selectors); + // Walk the shape backwards through the ops let mut shape = base_shape; for &op in ops_for_validation.iter().rev() { shape = match op { @@ -653,6 +719,7 @@ impl PropertyFilter { }; } + // Compute effective dtype let eff = match shape { Scalar(t) => t, List(t) => PropType::List(t), @@ -676,10 +743,10 @@ impl PropertyFilter { Scalar(t) => PropType::List(Box::new(t)), List(t) => PropType::List(Box::new(PropType::List(t))), Quantified(_, _) => { - let (base, qdepth_total) = flatten_quantified_depth(&*inner); - let qdepth_lists = qdepth_total.saturating_sub(1); // skip the temporal one + let (base, q_depth_total) = flatten_quantified_depth(&*inner); + let q_depth_lists = q_depth_total.saturating_sub(1); // skip the temporal one match base { - List(t) => Self::peel_list_n(PropType::List(t.clone()), qdepth_lists)?, + List(t) => Self::peel_list_n(PropType::List(t.clone()), q_depth_lists)?, Scalar(t) => t.clone(), _ => { return Err(GraphError::InvalidFilter( diff --git a/raphtory/src/python/filter/filter_expr.rs b/raphtory/src/python/filter/filter_expr.rs index 75b3a32469..8a7dd3525c 100644 --- a/raphtory/src/python/filter/filter_expr.rs +++ b/raphtory/src/python/filter/filter_expr.rs @@ -11,9 +11,11 @@ use crate::{ }, errors::GraphError, prelude::GraphViewOps, - python::filter::create_filter::DynInternalFilterOps, + python::filter::{ + create_filter::DynInternalFilterOps, property_filter_builders::PyPropertyFilterOps, + }, }; -use pyo3::prelude::*; +use pyo3::{exceptions::PyTypeError, prelude::*}; use std::sync::Arc; #[pyclass(frozen, name = "FilterExpr", module = "raphtory.filter")] @@ -62,3 +64,24 @@ impl CreateFilter for PyFilterExpr { self.0.create_filter(graph) } } + +pub enum AcceptFilter { + Expr(PyFilterExpr), + Chain(Py), +} + +impl<'py> FromPyObject<'py> for AcceptFilter { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + if let Ok(expr) = ob.extract::() { + return Ok(AcceptFilter::Expr(expr)); + } + + if let Ok(chain) = ob.extract::>() { + return Ok(AcceptFilter::Chain(chain)); + } + + Err(PyTypeError::new_err( + "Expected a FilterExpr or a PropertyFilterOps chain", + )) + } +} diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index 9c731c3f11..f44729b590 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -3,8 +3,8 @@ use crate::{ internal::CreateFilter, model::{ property_filter::{ - ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, - OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, + ElemQualifierOps, ListAggOps, MetadataFilterBuilder, OpChainBuilder, + PropertyFilterBuilder, PropertyFilterOps, }, TryAsCompositeFilter, }, @@ -18,7 +18,7 @@ use pyo3::{ use raphtory_api::core::entities::properties::prop::Prop; use std::sync::Arc; -pub trait DynPropertyFilterOps: Send + Sync { +pub trait DynFilterOps: Send + Sync { fn __eq__(&self, value: Prop) -> PyFilterExpr; fn __ne__(&self, value: Prop) -> PyFilterExpr; @@ -53,67 +53,219 @@ pub trait DynPropertyFilterOps: Send + Sync { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr; + + fn any(&self) -> PyResult; + + fn all(&self) -> PyResult; + + fn len(&self) -> PyResult; + + fn sum(&self) -> PyResult; + + fn avg(&self) -> PyResult; + + fn min(&self) -> PyResult; + + fn max(&self) -> PyResult; + + fn first(&self) -> PyResult; + + fn last(&self) -> PyResult; + + fn temporal(&self) -> PyResult; +} + +impl DynFilterOps for Arc { + #[inline] + fn __eq__(&self, v: Prop) -> PyFilterExpr { + (**self).__eq__(v) + } + + #[inline] + fn __ne__(&self, v: Prop) -> PyFilterExpr { + (**self).__ne__(v) + } + + #[inline] + fn __lt__(&self, v: Prop) -> PyFilterExpr { + (**self).__lt__(v) + } + + #[inline] + fn __le__(&self, v: Prop) -> PyFilterExpr { + (**self).__le__(v) + } + + #[inline] + fn __gt__(&self, v: Prop) -> PyFilterExpr { + (**self).__gt__(v) + } + + #[inline] + fn __ge__(&self, v: Prop) -> PyFilterExpr { + (**self).__ge__(v) + } + + #[inline] + fn is_in(&self, vs: FromIterable) -> PyFilterExpr { + (**self).is_in(vs) + } + + #[inline] + fn is_not_in(&self, vs: FromIterable) -> PyFilterExpr { + (**self).is_not_in(vs) + } + + #[inline] + fn is_none(&self) -> PyFilterExpr { + (**self).is_none() + } + + #[inline] + fn is_some(&self) -> PyFilterExpr { + (**self).is_some() + } + + #[inline] + fn starts_with(&self, v: Prop) -> PyFilterExpr { + (**self).starts_with(v) + } + + #[inline] + fn ends_with(&self, v: Prop) -> PyFilterExpr { + (**self).ends_with(v) + } + + #[inline] + fn contains(&self, v: Prop) -> PyFilterExpr { + (**self).contains(v) + } + + #[inline] + fn not_contains(&self, v: Prop) -> PyFilterExpr { + (**self).not_contains(v) + } + + #[inline] + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + (**self).fuzzy_search(prop_value, levenshtein_distance, prefix_match) + } + + #[inline] + fn any(&self) -> PyResult { + (**self).any() + } + + #[inline] + fn all(&self) -> PyResult { + (**self).all() + } + + #[inline] + fn len(&self) -> PyResult { + (**self).len() + } + + #[inline] + fn sum(&self) -> PyResult { + (**self).sum() + } + + #[inline] + fn avg(&self) -> PyResult { + (**self).avg() + } + + #[inline] + fn min(&self) -> PyResult { + (**self).min() + } + + #[inline] + fn max(&self) -> PyResult { + (**self).max() + } + + #[inline] + fn first(&self) -> PyResult { + (**self).first() + } + + #[inline] + fn last(&self) -> PyResult { + (**self).last() + } + + #[inline] + fn temporal(&self) -> PyResult { + (**self).temporal() + } } -impl DynPropertyFilterOps for F +impl DynFilterOps for PropertyFilterBuilder where - F: PropertyFilterOps, - PropertyFilter: CreateFilter + TryAsCompositeFilter, + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.eq(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ne(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.lt(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.le(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.gt(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ge(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_in(values))) + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_not_in(values))) + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_none())) + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_some())) + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.starts_with(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ends_with(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.contains(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.not_contains(value))) + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) } fn fuzzy_search( @@ -122,118 +274,118 @@ where levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.fuzzy_search( + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, prop_value, levenshtein_distance, prefix_match, ))) } -} -#[pyclass( - frozen, - name = "PropertyFilterOps", - module = "raphtory.filter", - subclass -)] -pub struct PyPropertyFilterOps { - ops: Arc, - agg: Arc, - qual: Arc, - sel: Arc, -} + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) + } -impl PyPropertyFilterOps { - fn from_parts( - ops_provider: Arc, - agg_provider: Arc, - qual_provider: Arc, - sel_provider: Arc, - ) -> Self - where - O: DynPropertyFilterOps + 'static, - A: DynListAggOps + 'static, - Q: DynElemQualifierOps + 'static, - S: DynSelectorOps + 'static, - { - PyPropertyFilterOps { - ops: ops_provider, - agg: agg_provider, - qual: qual_provider, - sel: sel_provider, - } - } - - fn from_builder(builder: B) -> Self - where - B: DynPropertyFilterOps + DynListAggOps + DynElemQualifierOps + DynSelectorOps + 'static, - { - let shared: Arc = Arc::new(builder); - PyPropertyFilterOps { - ops: shared.clone(), - agg: shared.clone(), - qual: shared.clone(), - sel: shared, - } + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) + } + + fn len(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) + } + + fn sum(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) + } + + fn avg(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) + } + + fn min(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) + } + + fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) + } + + fn first(&self) -> PyResult { + Err(PyTypeError::new_err( + "first() is only valid on temporal properties. Call temporal() first.", + )) + } + + fn last(&self) -> PyResult { + Err(PyTypeError::new_err( + "last() is only valid on temporal properties. Call temporal() first.", + )) + } + + fn temporal(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().temporal())) } } -#[pymethods] -impl PyPropertyFilterOps { +impl DynFilterOps for MetadataFilterBuilder +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ fn __eq__(&self, value: Prop) -> PyFilterExpr { - self.ops.__eq__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - self.ops.__ne__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - self.ops.__lt__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - self.ops.__le__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - self.ops.__gt__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - self.ops.__ge__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - self.ops.is_in(values) + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - self.ops.is_not_in(values) + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) } fn is_none(&self) -> PyFilterExpr { - self.ops.is_none() + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) } fn is_some(&self) -> PyFilterExpr { - self.ops.is_some() + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - self.ops.starts_with(value) + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - self.ops.ends_with(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - self.ops.contains(value) + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - self.ops.not_contains(value) + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) } fn fuzzy_search( @@ -242,305 +394,322 @@ impl PyPropertyFilterOps { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - self.ops - .fuzzy_search(prop_value, levenshtein_distance, prefix_match) - } - - pub fn first(&self) -> PyResult { - self.sel.first() - } - - pub fn last(&self) -> PyResult { - self.sel.last() + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, + prop_value, + levenshtein_distance, + prefix_match, + ))) } - pub fn any(&self) -> PyResult { - self.qual.any() + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) } - pub fn all(&self) -> PyResult { - self.qual.all() + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) } fn len(&self) -> PyResult { - self.agg.len() + Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) } fn sum(&self) -> PyResult { - self.agg.sum() + Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) } fn avg(&self) -> PyResult { - self.agg.avg() + Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) } fn min(&self) -> PyResult { - self.agg.min() + Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) } fn max(&self) -> PyResult { - self.agg.max() + Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) + } + + fn first(&self) -> PyResult { + Err(PyTypeError::new_err( + "first() is only valid on temporal properties.", + )) + } + + fn last(&self) -> PyResult { + Err(PyTypeError::new_err( + "last() is only valid on temporal properties.", + )) + } + + fn temporal(&self) -> PyResult { + Err(PyTypeError::new_err( + "temporal() is only valid on standard properties, not metadata.", + )) } } -pub trait DynListAggOps: Send + Sync { - fn len(&self) -> PyResult; +impl DynFilterOps for OpChainBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn __eq__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + } - fn sum(&self) -> PyResult; + fn __ne__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + } - fn avg(&self) -> PyResult; + fn __lt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + } - fn min(&self) -> PyResult; + fn __le__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + } - fn max(&self) -> PyResult; -} + fn __gt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + } -trait DynElemQualifierOps: Send + Sync { - fn any(&self) -> PyResult; + fn __ge__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + } - fn all(&self) -> PyResult; -} + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + } -#[derive(Clone)] -struct NoElemQualifiers; + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + } + + fn is_none(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + } + + fn is_some(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + } + + fn starts_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) + } + + fn ends_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) + } + + fn contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) + } + + fn not_contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) + } + + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, + prop_value, + levenshtein_distance, + prefix_match, + ))) + } -impl DynElemQualifierOps for NoElemQualifiers { fn any(&self) -> PyResult { - Err(PyTypeError::new_err("Element qualifiers (any/all) cannot be used after a list aggregation (len/sum/avg/min/max).")) + Ok(PyPropertyFilterOps::wrap(self.clone().any())) } fn all(&self) -> PyResult { - Err(PyTypeError::new_err("Element qualifiers (any/all) cannot be used after a list aggregation (len/sum/avg/min/max).")) + Ok(PyPropertyFilterOps::wrap(self.clone().all())) } -} - -#[derive(Clone)] -struct NoListAggOps; -impl DynListAggOps for NoListAggOps { fn len(&self) -> PyResult { - Err(PyTypeError::new_err( - "List aggregation len cannot be used after an element qualifier (any/all).", - )) + Ok(PyPropertyFilterOps::wrap(self.clone().len())) } fn sum(&self) -> PyResult { - Err(PyTypeError::new_err( - "List aggregation sum cannot be used after an element qualifier (any/all).", - )) + Ok(PyPropertyFilterOps::wrap(self.clone().sum())) } fn avg(&self) -> PyResult { - Err(PyTypeError::new_err( - "List aggregation avg cannot be used after an element qualifier (any/all).", - )) + Ok(PyPropertyFilterOps::wrap(self.clone().avg())) } fn min(&self) -> PyResult { - Err(PyTypeError::new_err( - "List aggregation min cannot be used after an element qualifier (any/all).", - )) + Ok(PyPropertyFilterOps::wrap(self.clone().min())) } fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().max())) + } + + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().first())) + } + + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().last())) + } + + fn temporal(&self) -> PyResult { Err(PyTypeError::new_err( - "List aggregation max cannot be used after an element qualifier (any/all).", + "temporal() must be called on a property builder, not on an existing chain.", )) } } -trait DynSelectorOps: Send + Sync { - fn first(&self) -> PyResult; +#[pyclass( + frozen, + name = "PropertyFilterOps", + module = "raphtory.filter", + subclass +)] +pub struct PyPropertyFilterOps { + ops: Arc, +} - fn last(&self) -> PyResult; +impl PyPropertyFilterOps { + fn wrap(t: T) -> Self { + Self { ops: Arc::new(t) } + } + + fn from_arc(ops: Arc) -> Self { + Self { ops } + } } -#[derive(Clone)] -struct NoSelector; +#[pymethods] +impl PyPropertyFilterOps { + fn __eq__(&self, value: Prop) -> PyFilterExpr { + self.ops.__eq__(value) + } -impl DynSelectorOps for NoSelector { - fn first(&self) -> PyResult { - Err(PyTypeError::new_err( - "first() is only valid on temporal properties.", - )) + fn __ne__(&self, value: Prop) -> PyFilterExpr { + self.ops.__ne__(value) } - fn last(&self) -> PyResult { - Err(PyTypeError::new_err( - "last() is only valid on temporal properties.", - )) + fn __lt__(&self, value: Prop) -> PyFilterExpr { + self.ops.__lt__(value) } -} -impl DynListAggOps for T -where - T: ListAggOps + InternalPropertyFilterOps + Clone + Send + Sync + 'static, - PropertyFilter<::Marker>: CreateFilter + TryAsCompositeFilter, -{ - fn len(&self) -> PyResult { - let ops = Arc::new(::len(&self)); - let agg = Arc::new(self.clone()); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) + fn __le__(&self, value: Prop) -> PyFilterExpr { + self.ops.__le__(value) } - fn sum(&self) -> PyResult { - let ops = Arc::new(::sum(&self)); - let agg = Arc::new(self.clone()); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) + fn __gt__(&self, value: Prop) -> PyFilterExpr { + self.ops.__gt__(value) } - fn avg(&self) -> PyResult { - let ops = Arc::new(::avg(&self)); - let agg = Arc::new(self.clone()); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) + fn __ge__(&self, value: Prop) -> PyFilterExpr { + self.ops.__ge__(value) } - fn min(&self) -> PyResult { - let ops = Arc::new(::min(&self)); - let agg = Arc::new(self.clone()); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + self.ops.is_in(values) } - fn max(&self) -> PyResult { - let ops = Arc::new(::max(&self)); - let agg = Arc::new(self.clone()); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - Ok(PyPropertyFilterOps::from_parts(ops, agg, qual, sel)) + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + self.ops.is_not_in(values) } -} -trait QualifierBehavior: - InternalPropertyFilterOps + ElemQualifierOps + Clone + Send + Sync + 'static -where - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn build_any(&self) -> PyPropertyFilterOps; + fn is_none(&self) -> PyFilterExpr { + self.ops.is_none() + } - fn build_all(&self) -> PyPropertyFilterOps; -} + fn is_some(&self) -> PyFilterExpr { + self.ops.is_some() + } -impl QualifierBehavior for PropertyFilterBuilder -where - M: Clone + Send + Sync + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn build_any(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::any(self)); - let agg = Arc::new(NoListAggOps); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) + fn starts_with(&self, value: Prop) -> PyFilterExpr { + self.ops.starts_with(value) } - fn build_all(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::all(self)); - let agg = Arc::new(NoListAggOps); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) + fn ends_with(&self, value: Prop) -> PyFilterExpr { + self.ops.ends_with(value) } -} -impl QualifierBehavior for MetadataFilterBuilder -where - M: Clone + Send + Sync + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn build_any(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::any(self)); - let agg = Arc::new(NoListAggOps); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) - } - fn build_all(&self) -> PyPropertyFilterOps { - let ops = Arc::new(ElemQualifierOps::all(self)); - let agg = Arc::new(NoListAggOps); - let qual = Arc::new(NoElemQualifiers); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) + fn contains(&self, value: Prop) -> PyFilterExpr { + self.ops.contains(value) } -} -impl DynSelectorOps for OpChainBuilder -where - M: Send + Sync + Clone + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn first(&self) -> PyResult { - Ok(PyPropertyFilterOps::from_builder(self.clone().first())) + fn not_contains(&self, value: Prop) -> PyFilterExpr { + self.ops.not_contains(value) } - fn last(&self) -> PyResult { - Ok(PyPropertyFilterOps::from_builder(self.clone().last())) + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + self.ops + .fuzzy_search(prop_value, levenshtein_distance, prefix_match) } -} -impl QualifierBehavior for OpChainBuilder -where - M: Send + Sync + Clone + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn build_any(&self) -> PyPropertyFilterOps { - let chain = self.clone().any(); - let ops = Arc::new(chain.clone()); - let agg = Arc::new(chain.clone()); - let qual = Arc::new(chain.clone()); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) - } - - fn build_all(&self) -> PyPropertyFilterOps { - let chain = self.clone().all(); - let ops = Arc::new(chain.clone()); - let agg = Arc::new(chain.clone()); - let qual = Arc::new(chain.clone()); - let sel = Arc::new(NoSelector); - PyPropertyFilterOps::from_parts(ops, agg, qual, sel) + pub fn first(&self) -> PyResult { + self.ops.first() } -} -impl DynElemQualifierOps for T -where - T: QualifierBehavior, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - fn any(&self) -> PyResult { - Ok(self.build_any()) + pub fn last(&self) -> PyResult { + self.ops.last() } - fn all(&self) -> PyResult { - Ok(self.build_all()) + pub fn any(&self) -> PyResult { + self.ops.any() + } + + pub fn all(&self) -> PyResult { + self.ops.all() + } + + fn len(&self) -> PyResult { + self.ops.len() + } + + fn sum(&self) -> PyResult { + self.ops.sum() + } + + fn avg(&self) -> PyResult { + self.ops.avg() + } + + fn min(&self) -> PyResult { + self.ops.min() + } + + fn max(&self) -> PyResult { + self.ops.max() } } -trait DynPropertyFilterBuilderOps: Send + Sync { - fn temporal(&self) -> PyPropertyFilterOps; +trait DynPropertyFilterBuilderOps: DynFilterOps { + fn as_dyn(self: Arc) -> Arc; } -impl DynPropertyFilterBuilderOps for PropertyFilterBuilder +impl DynPropertyFilterBuilderOps for T where - PropertyFilter: CreateFilter + TryAsCompositeFilter, + T: DynFilterOps + 'static, { - fn temporal(&self) -> PyPropertyFilterOps { - PyPropertyFilterOps::from_builder(self.clone().temporal()) + fn as_dyn(self: Arc) -> Arc { + self } } -/// Construct a property filter -/// -/// Arguments: -/// name (str): the name of the property to filter -#[pyclass(frozen, name = "Property", module = "raphtory.filter", extends=PyPropertyFilterOps +#[pyclass( + frozen, + name = "Property", + module = "raphtory.filter", + extends = PyPropertyFilterOps )] #[derive(Clone)] pub struct PyPropertyFilterBuilder(Arc); @@ -554,30 +723,25 @@ where type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - let inner = Arc::new(self); - let parent = PyPropertyFilterOps { - ops: inner.clone(), - agg: inner.clone(), - qual: inner.clone(), - sel: Arc::new(NoSelector), - }; - let child = PyPropertyFilterBuilder(inner as Arc); + let inner: Arc> = Arc::new(self); + let parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); + let child = PyPropertyFilterBuilder(inner); Bound::new(py, (child, parent)) } } #[pymethods] impl PyPropertyFilterBuilder { - fn temporal(&self) -> PyPropertyFilterOps { + fn temporal(&self) -> PyResult { self.0.temporal() } } -/// Construct a metadata filter -/// -/// Arguments: -/// name (str): the name of the property to filter -#[pyclass(frozen, name = "Metadata", module = "raphtory.filter", extends=PyPropertyFilterOps +#[pyclass( + frozen, + name = "Metadata", + module = "raphtory.filter", + extends = PyPropertyFilterOps )] #[derive(Clone)] pub struct PyMetadataFilterBuilder; @@ -591,12 +755,8 @@ where type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - let parent = PyPropertyFilterOps { - ops: Arc::new(self.clone()), - agg: Arc::new(self.clone()), - qual: Arc::new(self), - sel: Arc::new(NoSelector), - }; + let inner: Arc> = Arc::new(self); + let parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); let child = PyMetadataFilterBuilder; Bound::new(py, (child, parent)) } diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 9703842ffd..4614c211e5 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -24,7 +24,7 @@ use crate::{ errors::GraphError, prelude::PropertiesOps, python::{ - filter::filter_expr::PyFilterExpr, + filter::filter_expr::{AcceptFilter, PyFilterExpr}, graph::{ node::internal::BaseFilter, properties::{MetadataView, PropertiesView, PyMetadataListList, PyNestedPropsIterable}, diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index d5d1f9f09e..8f0afd18b2 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -33,7 +33,7 @@ use crate::{ errors::GraphError, prelude::*, python::{ - filter::filter_expr::PyFilterExpr, + filter::filter_expr::{AcceptFilter, PyFilterExpr}, graph::{edge::PyEdge, node::PyNode}, types::repr::{Repr, StructReprBuilder}, utils::PyNodeRef, 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..ee19eaa7bf 100644 --- a/raphtory/src/python/types/macros/trait_impl/filter_ops.rs +++ b/raphtory/src/python/types/macros/trait_impl/filter_ops.rs @@ -18,10 +18,35 @@ macro_rules! impl_filter_ops { #[doc=concat!(" ", $name, ": The filtered view")] fn filter( &self, - filter: PyFilterExpr, + filter: AcceptFilter, ) -> Result<<$base_type as BaseFilter<'static>>::Filtered, GraphError> { - Ok(self.$field.clone().filter(filter)?.into_dyn_hop()) + match filter { + AcceptFilter::Expr(expr) => { + Ok(self.$field.clone().filter(expr)?.into_dyn_hop()) + } + AcceptFilter::Chain(chain) => { + // Force Rust-side validation by turning the chain into a temporary FilterExpr. + pyo3::Python::with_gil(|py| -> Result<(), GraphError> { + // Call .is_some() to produce a FilterExpr (any operator is fine). + let obj = chain + .bind(py) + .call_method0("is_some") + .map_err(|e| GraphError::InvalidFilter(e.to_string()))?; + let probe: PyFilterExpr = obj + .extract() + .map_err(|e| GraphError::InvalidFilter(e.to_string()))?; + // This triggers resolve/validate in Rust: + let _ = self.$field.clone().filter(probe)?; + Ok(()) + })?; + + // If we get here, the chain was syntactically valid but not a full FilterExpr. + Err(GraphError::InvalidFilter( + "Expected FilterExpr; got a property chain. Add a comparison (e.g., ... > 2).".into(), + )) + } + } } } }; From 6de1a5c2bedeac1bc81b9ad9eb37cbb2b90dd570 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:10:30 +0100 Subject: [PATCH 06/42] rework gql filtering apis, fix tests --- .../test_graphql/test_apply_views.py | 101 +- .../test_filters/test_edge_filter_gql.py | 34 +- .../test_graph_edges_property_filter.py | 605 ++------ .../test_graph_nodes_property_filter.py | 639 ++------ .../test_filters/test_neighbours_filter.py | 51 +- .../test_filters/test_node_filter_gql.py | 63 +- .../test_nodes_property_filter.py | 711 +++------ raphtory-benchmark/benches/search_bench.rs | 40 +- raphtory-graphql/schema.graphql | 546 +------ raphtory-graphql/src/model/graph/filtering.rs | 1310 +++++++++-------- raphtory/src/db/graph/views/filter/mod.rs | 4 +- .../views/filter/model/filter_operator.rs | 34 +- .../src/db/graph/views/filter/model/mod.rs | 10 +- .../graph/views/filter/model/node_filter.rs | 11 +- .../views/filter/model/property_filter.rs | 6 +- raphtory/src/search/query_builder.rs | 12 +- 16 files changed, 1340 insertions(+), 2837 deletions(-) diff --git a/python/tests/test_base_install/test_graphql/test_apply_views.py b/python/tests/test_base_install/test_graphql/test_apply_views.py index ea6eb2ba22..68dc608c1f 100644 --- a/python/tests/test_base_install/test_graphql/test_apply_views.py +++ b/python/tests/test_base_install/test_graphql/test_apply_views.py @@ -1456,30 +1456,27 @@ def test_apply_view_node_filter(): graph = Graph() create_graph_date(graph) query = """ -{ - graph(path: "g") { - applyViews(views: [ - { - nodeFilter: { - property: { - name: "where" - operator: EQUAL - value: {str: "Berlin"} - + { + graph(path: "g") { + applyViews(views: [ + { + nodeFilter: { + property: { + name: "where" + where: { eq: { str: "Berlin" } } + } + } + } + ]) { + nodes { + list { + name + } } - } - } - - ]) { - nodes { - list { - name } } } - } - } -""" + """ correct = {"graph": {"applyViews": {"nodes": {"list": [{"name": "1"}]}}}} run_graphql_test(query, correct, graph) @@ -1488,30 +1485,27 @@ def test_apply_view_edge_filter(): graph = Graph() create_graph_date(graph) query = """ -{ - graph(path: "g") { - applyViews(views: [ - { - edgeFilter: { - property: { - name: "where" - operator: EQUAL - value: {str: "fishbowl"} - + { + graph(path: "g") { + applyViews(views: [ + { + edgeFilter: { + property: { + name: "where" + where: { eq: { str: "fishbowl" } } + } + } + } + ]) { + edges { + list { + history + } } - } - } - - ]) { - edges { - list { - history } } } - } - } -""" + """ correct = { "graph": {"applyViews": {"edges": {"list": [{"history": [1736035200000]}]}}} } @@ -1634,22 +1628,23 @@ def test_apply_view_a_lot_of_views(): graph = Graph() create_graph_date(graph) query = """ -{ - graph(path: "g") { - nodes{ - applyViews(views: [ - {window: {start: 1735689600000, end: 1735862400000}}, - {layer: "follows"}, - {nodeFilter: {property: {name: "where", operator: EQUAL, value: {str: "Berlin"}}}}, - ]) { - list { - name - history + { + graph(path: "g") { + nodes { + applyViews(views: [ + { window: { start: 1735689600000, end: 1735862400000 } }, + { layer: "follows" }, + { nodeFilter: { property: { name: "where", where: { eq: { str: "Berlin" } } } } } + ]) { + list { + name + history + } + } } } } -} -}""" + """ correct = { "graph": { "nodes": { 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 6d9dd5c767..e172d489cd 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 @@ -13,20 +13,15 @@ def test_filter_edges_with_str_ids_for_node_id_eq_gql(graph): query { graph(path: "g") { edgeFilter(filter: { - src: { - field: NODE_ID - operator: EQUAL - value: { str: "3" } - } + src: { + field: NODE_ID + where: { eq: { str: "3" } } + } }) { edges { list { - src { - name - } - dst { - name - } + src { name } + dst { name } } } } @@ -58,20 +53,15 @@ def test_filter_edges_with_num_ids_for_node_id_eq_gql(graph): query { graph(path: "g") { edgeFilter(filter: { - src: { - field: NODE_ID - operator: EQUAL - value: { u64: 1 } - } + src: { + field: NODE_ID + where: { eq: { u64: 1 } } + } }) { edges { list { - src { - name - } - dst { - name - } + 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 fd2fd0b263..43519c960a 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 @@ -7,7 +7,6 @@ PERSISTENT_GRAPH = create_test_graph(PersistentGraph()) -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_equal(graph): query = """ @@ -15,19 +14,13 @@ def test_graph_edge_property_filter_equal(graph): graph(path: "g") { edgeFilter( filter: { - property: { - name: "eprop5" - operator: EQUAL - value: { list: [{i64: 1},{i64: 2},{i64: 3}]} + property: { + name: "eprop5" + where: { eq: { list: [{i64: 1},{i64: 2},{i64: 3}] } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -43,52 +36,19 @@ def test_graph_edge_property_filter_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_equal_no_value_error(graph): +def test_graph_edge_property_filter_equal_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( filter: { - property: { - name: "eprop5" - operator: EQUAL + property: { + name: "eprop5" + where: { eq: { i64: 1 } } } } ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - -# Edge property filter is not supported yet for PersistentGraph -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop5" - operator: EQUAL - value: { i64: 1 } - } - } - ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -105,20 +65,14 @@ def test_graph_edge_property_filter_not_equal(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop4" - operator: NOT_EQUAL - value: { bool: true } - } - } - ) { - edges { - list { - src{name} - dst{name} + filter: { + property: { + name: "eprop4" + where: { ne: { bool: true } } } } + ) { + edges { list { src { name } dst { name } } } } } } @@ -133,34 +87,6 @@ def test_graph_edge_property_filter_not_equal(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_not_equal_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop4" - operator: NOT_EQUAL - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator NOT_EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_not_equal_type_error(graph): query = """ @@ -168,19 +94,13 @@ def test_graph_edge_property_filter_not_equal_type_error(graph): graph(path: "g") { edgeFilter( filter: { - property: { - name: "eprop4" - operator: NOT_EQUAL - value: { i64: 1 } + property: { + name: "eprop4" + where: { ne: { i64: 1 } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -191,7 +111,6 @@ def test_graph_edge_property_filter_not_equal_type_error(graph): run_graphql_error_test(query, expected_error_message, graph) -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_greater_than_or_equal(graph): query = """ @@ -199,19 +118,13 @@ def test_graph_edge_property_filter_greater_than_or_equal(graph): graph(path: "g") { edgeFilter( filter: { - property: { - name: "eprop1" - operator: GREATER_THAN_OR_EQUAL - value: { i64: 60 } + property: { + name: "eprop1" + where: { ge: { i64: 60 } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -227,55 +140,19 @@ def test_graph_edge_property_filter_greater_than_or_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_greater_than_or_equal_no_value_error(graph): +def test_graph_edge_property_filter_greater_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( filter: { - property: { - name: "eprop1" - operator: GREATER_THAN_OR_EQUAL + property: { + name: "eprop1" + where: { ge: { bool: true } } } } ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator GREATER_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -# Edge property filter is not supported yet for PersistentGraph -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_greater_than_or_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop1" - operator: GREATER_THAN_OR_EQUAL - value: { bool: true } - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -286,27 +163,20 @@ def test_graph_edge_property_filter_greater_than_or_equal_type_error(graph): run_graphql_error_test(query, expected_error_message, graph) -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_less_than_or_equal(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN_OR_EQUAL - value: { i64: 30 } - } - } - ) { - edges { - list { - src{name} - dst{name} + filter: { + property: { + name: "eprop1" + where: { le: { i64: 30 } } } } + ) { + edges { list { src { name } dst { name } } } } } } @@ -326,55 +196,15 @@ def test_graph_edge_property_filter_less_than_or_equal(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_less_than_or_equal_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN_OR_EQUAL - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator LESS_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN_OR_EQUAL - value: { str: "shivam" } - } - } + filter: { property: { name: "eprop1", where: { le: { str: "shivam" } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -391,20 +221,9 @@ def test_graph_edge_property_filter_greater_than(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: GREATER_THAN - value: { i64: 30 } - } - } + filter: { property: { name: "eprop1", where: { gt: { i64: 30 } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -419,54 +238,15 @@ def test_graph_edge_property_filter_greater_than(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_greater_than_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop1" - operator: GREATER_THAN - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator GREATER_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_greater_than_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: GREATER_THAN - value: { str: "shivam" } - } - } + filter: { property: { name: "eprop1", where: { gt: { str: "shivam" } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -477,27 +257,15 @@ def test_graph_edge_property_filter_greater_than_type_error(graph): run_graphql_error_test(query, expected_error_message, graph) -# Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_less_than(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN - value: { i64: 30 } - } - } + filter: { property: { name: "eprop1", where: { lt: { i64: 30 } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -512,53 +280,15 @@ def test_graph_edge_property_filter_less_than(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_less_than_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator LESS_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_less_than_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: LESS_THAN - value: { str: "shivam" } - } - } + filter: { property: { name: "eprop1", where: { lt: { str: "shivam" } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -575,19 +305,9 @@ def test_graph_edge_property_filter_is_none(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop5" - operator: IS_NONE - } - } + filter: { property: { name: "eprop5", where: { isNone: true } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -602,19 +322,9 @@ def test_graph_edge_property_filter_is_some(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop5" - operator: IS_SOME - } - } + filter: { property: { name: "eprop5", where: { isSome: true } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -641,20 +351,9 @@ def test_graph_edge_property_filter_is_in(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_IN - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} - } - } + filter: { property: { name: "eprop1", where: { isIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -680,20 +379,9 @@ def test_graph_edge_property_filter_is_empty_list(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_IN - value: { list: []} - } - } + filter: { property: { name: "eprop1", where: { isIn: { list: [] } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -702,59 +390,21 @@ def test_graph_edge_property_filter_is_empty_list(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_is_in_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator IS_IN requires a list" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_is_in_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_IN - value: { str: "shivam" } - } - } + filter: { property: { name: "eprop1", where: { isIn: { str: "shivam" } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } """ expected_error_message = ( - "Invalid filter: Operator IS_IN requires a list value, got Str(shivam)" + "Invalid filter: isIn requires a list value, got Str(shivam)" ) run_graphql_error_test(query, expected_error_message, graph) @@ -765,20 +415,9 @@ def test_graph_edge_property_filter_is_not_in(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_NOT_IN - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} - } - } + filter: { property: { name: "eprop1", where: { isNotIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -799,20 +438,9 @@ def test_graph_edge_property_filter_is_not_in_empty_list(graph): query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_NOT_IN - value: { list: []} - } - } + filter: { property: { name: "eprop1", where: { isNotIn: { list: [] } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -833,59 +461,21 @@ def test_graph_edge_property_filter_is_not_in_empty_list(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_edge_property_filter_is_not_in_no_value_error(graph): - query = """ - query { - graph(path: "g") { - edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_NOT_IN - } - } - ) { - edges { - list { - src{name} - dst{name} - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator IS_NOT_IN requires a list" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_edge_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { edgeFilter( - filter: { - property: { - name: "eprop1" - operator: IS_NOT_IN - value: { str: "shivam" } - } - } + filter: { property: { name: "eprop1", where: { isNotIn: { str: "shivam" } } } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } """ expected_error_message = ( - "Invalid filter: Operator IS_NOT_IN requires a list value, got Str(shivam)" + "Invalid filter: isNotIn requires a list value, got Str(shivam)" ) run_graphql_error_test(query, expected_error_message, graph) @@ -897,22 +487,15 @@ def test_graph_edge_not_property_filter(graph): graph(path: "g") { edgeFilter ( filter: { - not: - { - property: { - name: "eprop5" - operator: EQUAL - value: { list: [{i64: 1},{i64: 2}]} - } + not: { + property: { + name: "eprop5" + where: { eq: { list: [{i64: 1},{i64: 2}] } } } + } } ) { - edges { - list { - src{name} - dst{name} - } - } + edges { list { src { name } dst { name } } } } } } @@ -936,24 +519,18 @@ def test_graph_edge_not_property_filter(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_edges_property_filter_starts_with(graph): query = """ - query { - graph(path: "g") { - edgeFilter(filter: { - property: { - name: "eprop3" - operator: STARTS_WITH - value: { str: "xyz" } - } - }) { - edges { - list { - src { name } - dst { name } - } - } - } + query { + graph(path: "g") { + edgeFilter(filter: { + property: { + name: "eprop3" + where: { startsWith: { str: "xyz" } } } + }) { + edges { list { src { name } dst { name } } } } + } + } """ expected_output = { "graph": { @@ -974,24 +551,18 @@ def test_edges_property_filter_starts_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_edges_property_filter_ends_with(graph): query = """ - query { - graph(path: "g") { - edgeFilter(filter: { - property: { - name: "eprop3" - operator: ENDS_WITH - value: { str: "123" } - } - }) { - edges { - list { - src { name } - dst { name } - } - } - } + query { + graph(path: "g") { + edgeFilter(filter: { + property: { + name: "eprop3" + where: { endsWith: { str: "123" } } } + }) { + edges { list { src { name } dst { name } } } } + } + } """ expected_output = { "graph": { diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py index f6b238df81..95128896ea 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py @@ -9,55 +9,24 @@ @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_node_property_filter_equal(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop5" - operator: EQUAL - value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } - } - } - ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} - run_graphql_test(query, expected_output, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_equal_no_value_error(graph): query = """ query { graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop5" - operator: EQUAL + property: { + name: "prop5" + where: { eq: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } """ - expected_error_message = "Invalid filter: Operator EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) + expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + run_graphql_test(query, expected_output, graph) @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) @@ -67,18 +36,13 @@ def test_graph_node_property_filter_equal_type_error(graph): graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop5" - operator: EQUAL - value: { i64: 1 } + property: { + name: "prop5" + where: { eq: { i64: 1 } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -96,18 +60,13 @@ def test_graph_node_property_filter_not_equal(graph): graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop4" - operator: NOT_EQUAL - value: { bool: true } + property: { + name: "prop4" + where: { ne: { bool: true } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -118,51 +77,20 @@ def test_graph_node_property_filter_not_equal(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_not_equal_no_value_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop4" - operator: NOT_EQUAL - } - } - ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator NOT_EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_node_property_filter_not_equal_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop4" - operator: NOT_EQUAL - value: { i64: 1 } - } - } - ) { - nodes { - list { - name + filter: { + property: { + name: "prop4" + where: { ne: { i64: 1 } } } } + ) { + nodes { list { name } } } } } @@ -180,18 +108,13 @@ def test_graph_node_property_filter_greater_than_or_equal(graph): graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL - value: { i64: 60 } + property: { + name: "prop1" + where: { ge: { i64: 60 } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -201,52 +124,19 @@ def test_graph_node_property_filter_greater_than_or_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_greater_than_or_equal_no_value_error(graph): +def test_graph_node_property_filter_greater_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL + property: { + name: "prop1" + where: { ge: { bool: true } } } } ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator GREATER_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_greater_than_or_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL - value: { bool: true } - } - } - ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -264,18 +154,10 @@ def test_graph_node_property_filter_less_than_or_equal(graph): graph(path: "g") { nodeFilter( filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL - value: { i64: 30 } - } + property: { name: "prop1", where: { le: { i64: 30 } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -291,52 +173,14 @@ def test_graph_node_property_filter_less_than_or_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_less_than_or_equal_no_value_error(graph): +def test_graph_node_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL - } - } + filter: { property: { name: "prop1", where: { le: { str: "shivam" } } } } ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator LESS_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_less_than_or_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL - value: { str: "shivam" } - } - } - ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -353,19 +197,9 @@ def test_graph_node_property_filter_greater_than(graph): query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN - value: { i64: 30 } - } - } + filter: { property: { name: "prop1", where: { gt: { i64: 30 } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -374,51 +208,15 @@ def test_graph_node_property_filter_greater_than(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_greater_than_no_value_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN - } - } - ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator GREATER_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_node_property_filter_greater_than_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN - value: { str: "shivam" } - } - } - ) { - nodes { - list { - name - } - } + filter: { property: { name: "prop1", where: { gt: { str: "shivam" } } } } + ) { + nodes { list { name } } } } } @@ -435,19 +233,9 @@ def test_graph_node_property_filter_less_than(graph): query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN - value: { i64: 30 } - } - } + filter: { property: { name: "prop1", where: { lt: { i64: 30 } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -458,51 +246,15 @@ def test_graph_node_property_filter_less_than(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_less_than_no_value_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN - } - } - ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator LESS_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_node_property_filter_less_than_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN - value: { str: "shivam" } - } - } - ) { - nodes { - list { - name - } - } + filter: { property: { name: "prop1", where: { lt: { str: "shivam" } } } } + ) { + nodes { list { name } } } } } @@ -519,18 +271,9 @@ def test_graph_node_property_filter_is_none(graph): query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop5" - operator: IS_NONE - } - } + filter: { property: { name: "prop5", where: { isNone: true } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -546,19 +289,10 @@ def test_graph_node_property_filter_is_some(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop5" - operator: IS_SOME - } - } - ) { - nodes { - list { - name - } - } + nodeFilter( + filter: { property: { name: "prop5", where: { isSome: true } } } + ) { + nodes { list { name } } } } } @@ -575,19 +309,9 @@ def test_graph_node_property_filter_is_in(graph): query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} - } - } + filter: { property: { name: "prop1", where: { isIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } @@ -604,18 +328,10 @@ def test_node_property_filter_is_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: []} - } - } - ) { - list { - name - } + nodeFilter( + filter: { property: { name: "prop1", where: { isIn: { list: [] } } } } + ) { + list { name } } } } @@ -627,23 +343,14 @@ def test_node_property_filter_is_in_empty_list(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_node_property_filter_is_in_no_value(graph): + # With where-shape, an empty list is a valid value (yields empty result). query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: []} - } - } - ) { - nodes { - list { - name - } - } + filter: { property: { name: "prop1", where: { isIn: { list: [] } } } } + ) { + nodes { list { name } } } } } @@ -658,25 +365,15 @@ def test_graph_node_property_filter_is_in_type_error(graph): query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - value: { str: "shivam" } - } - } + filter: { property: { name: "prop1", where: { isIn: { str: "shivam" } } } } ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } """ expected_error_message = ( - "Invalid filter: Operator IS_IN requires a list value, got Str(shivam)" + "Invalid filter: isIn requires a list value, got Str(shivam)" ) run_graphql_error_test(query, expected_error_message, graph) @@ -686,20 +383,10 @@ def test_graph_node_property_filter_is_not_in_any(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} - } - } - ) { - nodes { - list { - name - } - } + nodeFilter( + filter: { property: { name: "prop1", where: { isNotIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } + ) { + nodes { list { name } } } } } @@ -716,18 +403,10 @@ def test_node_property_filter_not_is_not_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { list: []} - } - } + nodeFilter( + filter: { property: { name: "prop1", where: { isNotIn: { list: [] } } } } ) { - list { - name - } + list { name } } } } @@ -746,56 +425,20 @@ def test_node_property_filter_not_is_not_in_empty_list(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_is_not_in_no_value_error(graph): +def test_graph_node_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - } - } + filter: { property: { name: "prop1", where: { isNotIn: { str: "shivam" } } } } ) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator IS_NOT_IN requires a list" - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_node_property_filter_is_not_in_type_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { str: "shivam" } - } - } - ) { - nodes { - list { - name - } - } + nodes { list { name } } } } } """ expected_error_message = ( - "Invalid filter: Operator IS_NOT_IN requires a list value, got Str(shivam)" + "Invalid filter: isNotIn requires a list value, got Str(shivam)" ) run_graphql_error_test(query, expected_error_message, graph) @@ -807,21 +450,15 @@ def test_graph_node_not_property_filter(graph): graph(path: "g") { nodeFilter ( filter: { - not: - { - property: { - name: "prop5" - operator: EQUAL - value: { list: [ {i64: 1}, {i64: 2} ] } - } + not: { + property: { + name: "prop5" + where: { eq: { list: [ {i64: 1}, {i64: 2} ] } } } - } - ) { - nodes { - list { - name } } + ) { + nodes { list { name } } } } } @@ -845,29 +482,23 @@ def test_graph_node_type_and_property_filter(graph): graph(path: "g") { nodes { nodeFilter(filter: { - and: [{ - node: { - field: NODE_TYPE, - operator: IS_IN, - value: { - list: [ - {str: "fire_nation"}, - {str: "water_tribe"} - ] - } - } - },{ - property: { - name: "prop2", - operator: GREATER_THAN, - value: { f64:1 } + and: [ + { + node: { + field: NODE_TYPE, + where: { isIn: { list: [ {str: "fire_nation"}, {str: "water_tribe"} ] } } + } + }, + { + property: { + name: "prop2", + where: { gt: { f64: 1 } } + } } - }] + ] }) { count - list { - name - } + list { name } } } } @@ -889,23 +520,18 @@ def test_graph_node_type_and_property_filter(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_nodes_property_filter_starts_with(graph): query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - property: { - name: "prop3" - operator: STARTS_WITH - value: { str: "abc" } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + property: { + name: "prop3" + where: { startsWith: { str: "abc" } } } + }) { + nodes { list { name } } } + } + } """ expected_output = { "graph": { @@ -922,51 +548,40 @@ def test_graph_nodes_property_filter_starts_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_graph_nodes_property_filter_ends_with(graph): query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - property: { - name: "prop3" - operator: ENDS_WITH - value: { str: "123" } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + property: { + name: "prop3" + where: { endsWith: { str: "123" } } } + }) { + nodes { list { name } } } + } + } """ expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_graph_nodes_property_filter_starts_with(graph): +def test_graph_nodes_property_filter_starts_with_temporal_any(graph): query = """ - query { - graph(path: "g") { - nodeFilter( - filter: { - temporalProperty: { - name: "prop3", - ops: [ALL], - operator: STARTS_WITH - value: { str: "abc1" } - } - } - ) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter( + filter: { + temporalProperty: { + name: "prop3", + where: { any: { startsWith: { str: "abc1" } } } + } } + ) { + nodes { list { name } } } + } + } """ expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py index 8f02c924f7..678a295d6e 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py @@ -18,23 +18,19 @@ def test_out_neighbours_found(graph): { node: { field: NODE_NAME, - operator: EQUAL, - value:{ str: "d" } + where: { eq: { str: "d" } } } }, { property: { name: "prop1" - operator: GREATER_THAN - value: { i64: 10 } + where: { gt: { i64: 10 } } } } ] }) { outNeighbours { - list { - name - } + list { name } } } } @@ -56,14 +52,11 @@ def test_out_neighbours_not_found(graph): nodeFilter(filter: { node: { field: NODE_NAME, - operator: EQUAL, - value:{ str: "e" } + where: { eq: { str: "e" } } } }) { outNeighbours { - list { - name - } + list { name } } } } @@ -83,16 +76,13 @@ def test_in_neighbours_found(graph): graph(path: "g") { node(name: "d") { nodeFilter(filter: { - property: { - name: "prop1" - operator: GREATER_THAN - value: { i64: 10 } - } + property: { + name: "prop1" + where: { gt: { i64: 10 } } + } }) { inNeighbours { - list { - name - } + list { name } } } } @@ -118,14 +108,11 @@ def test_in_neighbours_not_found(graph): nodeFilter(filter: { node: { field: NODE_NAME, - operator: EQUAL, - value:{ str: "e" } + where: { eq: { str: "e" } } } }) { inNeighbours { - list { - name - } + list { name } } } } @@ -147,14 +134,11 @@ def test_neighbours_found(graph): nodeFilter(filter: { node: { field: NODE_NAME, - operator: NOT_EQUAL, - value:{ str: "a" } + where: { ne: { str: "a" } } } }) { neighbours { - list { - name - } + list { name } } } } @@ -180,14 +164,11 @@ def test_neighbours_not_found(graph): nodeFilter(filter: { node: { field: NODE_NAME, - operator: EQUAL, - value:{ str: "e" } + where: { eq: { str: "e" } } } }) { neighbours { - list { - name - } + list { name } } } } diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py index 985aafa7fe..c0e9737024 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py @@ -13,19 +13,16 @@ def test_filter_nodes_with_str_ids_for_node_id_eq_gql(graph): query { graph(path: "g") { nodeFilter( - filter: { - node: { - field: NODE_ID - operator: EQUAL - value: { str: "1" } - } + filter: { + node: { + field: NODE_ID + where: { eq: { str: "1" } } } - ) { - nodes { - list { - name - } - } + } + ) { + nodes { + list { name } + } } } } @@ -40,19 +37,16 @@ def test_filter_nodes_with_str_ids_for_node_id_eq_gql2(graph): query { graph(path: "g") { nodeFilter( - filter: { - node: { - field: NODE_ID - operator: EQUAL - value: { u64: 1 } - } + filter: { + node: { + field: NODE_ID + where: { eq: { u64: 1 } } } - ) { - nodes { - list { - name - } - } + } + ) { + nodes { + list { name } + } } } } @@ -71,19 +65,16 @@ def test_filter_nodes_with_num_ids_for_node_id_eq_gql(graph): query { graph(path: "g") { nodeFilter( - filter: { - node: { - field: NODE_ID - operator: EQUAL - value: { u64: 1 } - } + filter: { + node: { + field: NODE_ID + where: { eq: { u64: 1 } } } - ) { - nodes { - list { - name - } - } + } + ) { + nodes { + list { name } + } } } } diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index e2859e4ccd..54923bb4e9 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -18,41 +18,16 @@ def test_node_property_filter_equal(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { - property: { - name: "prop5" - operator: EQUAL - value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } - } - } - ) { - list { - name - } - } - } - } - } - """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} - run_graphql_test(query, expected_output, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_equal_no_value_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop5" - operator: EQUAL + nodeFilter( + filter: { + property: { + name: "prop5" + where: { + eq: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } } } - ) { + } + ) { list { name } @@ -61,8 +36,8 @@ def test_node_property_filter_equal_no_value_error(graph): } } """ - expected_error_message = "Invalid filter: Operator EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) + expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} + run_graphql_test(query, expected_output, graph) @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) @@ -71,15 +46,16 @@ def test_node_property_filter_equal_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { - property: { - name: "prop5" - operator: EQUAL - value: { i64: 1 } + nodeFilter( + filter: { + property: { + name: "prop5" + where: { + eq: { i64: 1 } } } - ) { + } + ) { list { name } @@ -99,16 +75,17 @@ def test_node_property_filter_not_equal(graph): query = """ query { graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop4" - operator: NOT_EQUAL - value: { bool: true } + nodes { + nodeFilter( + filter: { + property: { + name: "prop4" + where: { + ne: { bool: true } } } - ) { + } + ) { list { name } @@ -124,46 +101,21 @@ def test_node_property_filter_not_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_not_equal_no_value_error(graph): +def test_node_property_filter_not_equal_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop4" - operator: NOT_EQUAL + filter: { + property: { + name: "prop4" + where: { + ne: { i64: 1 } } } - ) { - list { - name } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator NOT_EQUAL requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_not_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop4" - operator: NOT_EQUAL - value: { i64: 1 } - } - } - ) { + ) { list { name } @@ -186,10 +138,11 @@ def test_node_property_filter_greater_than_or_equal(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL - value: { i64: 60 } + property: { + name: "prop1" + where: { + ge: { i64: 60 } + } } } ) { @@ -206,16 +159,18 @@ def test_node_property_filter_greater_than_or_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_greater_than_or_equal_no_value_error(graph): +def test_node_property_filter_greater_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL + property: { + name: "prop1" + where: { + ge: { bool: true } + } } } ) { @@ -227,35 +182,6 @@ def test_node_property_filter_greater_than_or_equal_no_value_error(graph): } } """ - expected_error_message = ( - "Invalid filter: Operator GREATER_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_greater_than_or_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN_OR_EQUAL - value: { bool: true } - } - } - ) { - list { - name - } - } - } - } - } - """ expected_error_message = ( "Wrong type for property prop1: expected I64 but actual type is Bool" ) @@ -270,10 +196,11 @@ def test_node_property_filter_less_than_or_equal(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL - value: { i64: 30 } + property: { + name: "prop1" + where: { + le: { i64: 30 } + } } } ) { @@ -296,51 +223,20 @@ def test_node_property_filter_less_than_or_equal(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_less_than_or_equal_no_value_error(graph): +def test_node_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL + property: { + name: "prop1" + where: { le: { str: "shivam" } } } } ) { - list { - name - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator LESS_THAN_OR_EQUAL requires a value" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_less_than_or_equal_type_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN_OR_EQUAL - value: { str: "shivam" } - } - } - ) { - list { - name - } + list { name } } } } @@ -360,16 +256,13 @@ def test_node_property_filter_greater_than(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN - value: { i64: 30 } + property: { + name: "prop1" + where: { gt: { i64: 30 } } } } ) { - list { - name - } + list { name } } } } @@ -379,32 +272,6 @@ def test_node_property_filter_greater_than(graph): run_graphql_test(query, expected_output, graph) -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_greater_than_no_value_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: GREATER_THAN - } - } - ) { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator GREATER_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_node_property_filter_greater_than_type_error(graph): query = """ @@ -413,16 +280,13 @@ def test_node_property_filter_greater_than_type_error(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: GREATER_THAN - value: { str: "shivam" } + property: { + name: "prop1" + where: { gt: { str: "shivam" } } } } - ) { - list { - name - } + ) { + list { name } } } } @@ -441,17 +305,14 @@ def test_node_property_filter_less_than(graph): graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN - value: { i64: 30 } - } + filter: { + property: { + name: "prop1" + where: { lt: { i64: 30 } } } - ) { - list { - name } + ) { + list { name } } } } @@ -464,49 +325,20 @@ def test_node_property_filter_less_than(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_less_than_no_value_error(graph): +def test_node_property_filter_less_than_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: LESS_THAN + property: { + name: "prop1" + where: { lt: { str: "shivam" } } } } ) { - list { - name - } - } - } - } - } - """ - expected_error_message = "Invalid filter: Operator LESS_THAN requires a value" - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_less_than_type_error(graph): - query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: LESS_THAN - value: { str: "shivam" } - } - } - ) { - list { - name - } + list { name } } } } @@ -526,15 +358,13 @@ def test_node_property_filter_is_none(graph): nodes { nodeFilter( filter: { - property: { - name: "prop5" - operator: IS_NONE + property: { + name: "prop5" + where: { isNone: true } } } ) { - list { - name - } + list { name } } } } @@ -553,16 +383,14 @@ def test_node_property_filter_is_some(graph): graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop5" - operator: IS_SOME - } + filter: { + property: { + name: "prop5" + where: { isSome: true } } - ) { - list { - name } + ) { + list { name } } } } @@ -582,16 +410,13 @@ def test_node_property_filter_is_in(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + property: { + name: "prop1" + where: { isIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } ) { - list { - name - } + list { name } } } } @@ -611,16 +436,13 @@ def test_node_property_filter_is_in_empty_list(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: []} + property: { + name: "prop1" + where: { isIn: { list: [] } } } } ) { - list { - name - } + list { name } } } } @@ -632,22 +454,20 @@ def test_node_property_filter_is_in_empty_list(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_node_property_filter_is_in_no_value(graph): + # Keeping semantics: value list has no matching elements query = """ query { graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_IN - value: { list: [{i64: 100}]} - } + filter: { + property: { + name: "prop1" + where: { isIn: { list: [{i64: 100}] } } } - ) { - list { - name } + ) { + list { name } } } } @@ -665,23 +485,20 @@ def test_node_property_filter_is_in_type_error(graph): nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: IS_IN - value: { str: "shivam" } + property: { + name: "prop1" + where: { isIn: { str: "shivam" } } } } ) { - list { - name - } + list { name } } } } } """ expected_error_message = ( - "Invalid filter: Operator IS_IN requires a list value, got Str(shivam)" + "Invalid filter: isIn requires a list value, got Str(shivam)" ) run_graphql_error_test(query, expected_error_message, graph) @@ -693,17 +510,14 @@ def test_node_property_filter_is_not_in(graph): graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} - } + filter: { + property: { + name: "prop1" + where: { isNotIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } - ) { - list { - name } + ) { + list { name } } } } @@ -722,17 +536,14 @@ def test_node_property_filter_is_not_in_empty_list(graph): graph(path: "g") { nodes { nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { list: []} - } + filter: { + property: { + name: "prop1" + where: { isNotIn: { list: [] } } } - ) { - list { - name } + ) { + list { name } } } } @@ -751,109 +562,70 @@ def test_node_property_filter_is_not_in_empty_list(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_is_not_in_no_value_error(graph): +def test_node_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( filter: { - property: { - name: "prop1" - operator: IS_NOT_IN + property: { + name: "prop1" + where: { isNotIn: { str: "shivam" } } } } ) { - list { - name - } + list { name } } } } } """ - expected_error_message = "Invalid filter: Operator IS_NOT_IN requires a list" + expected_error_message = ( + "Invalid filter: isNotIn requires a list value, got Str(shivam)" + ) run_graphql_error_test(query, expected_error_message, graph) @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_is_not_in_type_error(graph): +def test_node_property_filter_contains_wrong_value_type_error(graph): query = """ query { graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop1" - operator: IS_NOT_IN - value: { str: "shivam" } - } - } - ) { - list { - name - } + nodeFilter(filter: { + property: { + name: "p10" + where: { contains: { u64: 2 } } + } + }) { + nodes { + list { name } } } } } """ - expected_error_message = ( - "Invalid filter: Operator IS_NOT_IN requires a list value, got Str(shivam)" - ) - run_graphql_error_test(query, expected_error_message, graph) - - -@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_contains_wrong_value_type_error(graph): - query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - property: { - name: "p10" - operator: CONTAINS - value: { u64: 2 } - } - }) { - nodes { - list { - name - } - } - } - } - } - """ - expected_error_message = ( - "Invalid filter: Operator CONTAINS requires a string value, got U64(2)" - ) + expected_error_message = "Property p10 does not exist" run_graphql_error_test(query, expected_error_message, graph) @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_property_filter_starts_with(graph): query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop3" - operator: STARTS_WITH - value: { str: "abc" } - } - } - ) { - list { - name - } - } + query { + graph(path: "g") { + nodes { + nodeFilter(filter: { + property: { + name: "prop3" + where: { startsWith: { str: "abc" } } } + }) { + list { name } } } + } + } """ expected_output = { "graph": { @@ -870,25 +642,20 @@ def test_nodes_property_filter_starts_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_property_filter_ends_with(graph): query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - property: { - name: "prop3" - operator: ENDS_WITH - value: { str: "333" } - } - } - ) { - list { - name - } - } + query { + graph(path: "g") { + nodes { + nodeFilter(filter: { + property: { + name: "prop3" + where: { endsWith: { str: "333" } } } + }) { + list { name } } } + } + } """ expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "c"}]}}}} run_graphql_test(query, expected_output, graph) @@ -897,26 +664,20 @@ def test_nodes_property_filter_ends_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_property_filter_temporal_first_starts_with(graph): query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - temporalProperty: { - name: "prop3", - ops: [FIRST] - operator: STARTS_WITH - value: { str: "abc" } - } - } - ) { - list { - name - } - } + query { + graph(path: "g") { + nodes { + nodeFilter(filter: { + temporalProperty: { + name: "prop3" + where: { first: { startsWith: { str: "abc" } } } } + }) { + list { name } } } + } + } """ expected_output = { "graph": { @@ -931,28 +692,22 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_property_filter_temporal_first_starts_with(graph): +def test_nodes_property_filter_temporal_all_starts_with(graph): query = """ - query { - graph(path: "g") { - nodes { - nodeFilter( - filter: { - temporalProperty: { - name: "prop3", - ops: [ALL] - operator: STARTS_WITH - value: { str: "abc1" } - } - } - ) { - list { - name - } - } + query { + graph(path: "g") { + nodes { + nodeFilter(filter: { + temporalProperty: { + name: "prop3" + where: { any: { startsWith: { str: "abc1" } } } } + }) { + list { name } } } + } + } """ expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -960,25 +715,20 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_property_filter_list_agg(graph): + # SUM(list(prop5)) == 6 query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - property: { - name: "prop5" - operator: EQUAL - ops: [SUM] - value: { i64: 6 } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + property: { + name: "prop5" + where: { sum: { eq: { i64: 6 } } } } + }) { + nodes { list { name } } } + } + } """ expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -987,24 +737,18 @@ def test_nodes_property_filter_list_agg(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_property_filter_list_qualifier(graph): query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - property: { - name: "prop5" - operator: EQUAL - ops: [ANY] - value: { i64: 6 } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + property: { + name: "prop5" + where: { any: { eq: { i64: 6 } } } } + }) { + nodes { list { name } } } + } + } """ expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "c"}]}}}} run_graphql_test(query, expected_output, graph) @@ -1017,24 +761,18 @@ def test_nodes_property_filter_list_qualifier(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_temporal_property_filter_agg(graph): query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - temporalProperty: { - name: "p2" - operator: LESS_THAN - ops: [AVG] - value: { f64: 10.0 } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + temporalProperty: { + name: "p2" + where: { avg: { lt: { f64: 10.0 } } } } + }) { + nodes { list { name } } } + } + } """ expected_output = { "graph": {"nodeFilter": {"nodes": {"list": [{"name": "2"}, {"name": "3"}]}}} @@ -1048,25 +786,20 @@ def test_nodes_temporal_property_filter_agg(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_nodes_temporal_property_filter_any_avg(graph): + # ANY timepoint where AVG(list) < 10.0 query = """ - query { - graph(path: "g") { - nodeFilter(filter: { - temporalProperty: { - name: "prop5" - operator: LESS_THAN - ops: [ANY, AVG] - value: { f64: 10.0 } - } - }) { - nodes { - list { - name - } - } - } + query { + graph(path: "g") { + nodeFilter(filter: { + temporalProperty: { + name: "prop5" + where: { any: { avg: { lt: { f64: 10.0 } } } } } + }) { + nodes { list { name } } } + } + } """ expected_output = { "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} diff --git a/raphtory-benchmark/benches/search_bench.rs b/raphtory-benchmark/benches/search_bench.rs index 11966812d1..8d685ebe85 100644 --- a/raphtory-benchmark/benches/search_bench.rs +++ b/raphtory-benchmark/benches/search_bench.rs @@ -218,16 +218,20 @@ where match filter_op { Eq => Some(M::property(prop_name).eq(sub_str)), Ne => Some(M::property(prop_name).ne(sub_str)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => { + sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)) + } _ => None, // No numeric comparison for strings } } else { match filter_op { Eq => Some(M::property(prop_name).eq(full_str)), Ne => Some(M::property(prop_name).ne(full_str)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => { + sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)) + } _ => None, // No numeric comparison for strings } } @@ -244,8 +248,8 @@ where Le => Some(M::property(prop_name).le(v)), Gt => Some(M::property(prop_name).gt(v)), Ge => Some(M::property(prop_name).ge(v)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::I64 => prop_value.into_i64().and_then(|v| match filter_op { @@ -255,8 +259,8 @@ where Le => Some(M::property(prop_name).le(v)), Gt => Some(M::property(prop_name).gt(v)), Ge => Some(M::property(prop_name).ge(v)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::F64 => prop_value.into_f64().and_then(|v| match filter_op { @@ -266,15 +270,15 @@ where Le => Some(M::property(prop_name).le(v)), Gt => Some(M::property(prop_name).gt(v)), Ge => Some(M::property(prop_name).ge(v)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::Bool => prop_value.into_bool().and_then(|v| match filter_op { Eq => Some(M::property(prop_name).eq(v)), Ne => Some(M::property(prop_name).ne(v)), - In => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), - NotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), + IsIn => sampled_values.map(|vals| M::property(prop_name).is_in(vals)), + IsNotIn => sampled_values.map(|vals| M::property(prop_name).is_not_in(vals)), _ => return None, }), @@ -326,7 +330,7 @@ fn pick_node_property_filter( }?; let sampled_values = match filter_op { - In | NotIn => Some(get_node_property_samples(graph, prop_id, is_const)), + IsIn | IsNotIn => Some(get_node_property_samples(graph, prop_id, is_const)), _ => None, }; @@ -436,7 +440,7 @@ fn pick_edge_property_filter( }?; let sampled_values = match filter_op { - In | NotIn => Some(get_edge_property_samples(graph, prop_id, is_const)), + IsIn | IsNotIn => Some(get_edge_property_samples(graph, prop_id, is_const)), _ => None, }; @@ -559,8 +563,8 @@ macro_rules! bench_search_nodes_by_property_filter { "lt" => FilterOperator::Lt, "ge" => FilterOperator::Ge, "gt" => FilterOperator::Gt, - "in" => FilterOperator::In, - "not_in" => FilterOperator::NotIn, + "is_in" => FilterOperator::IsIn, + "is_not_in" => FilterOperator::IsNotIn, _ => panic!("Unknown filter type in function name"), }; bench_search_nodes_by_property_filter::( @@ -629,8 +633,8 @@ macro_rules! bench_search_edges_by_property_filter { "lt" => FilterOperator::Lt, "ge" => FilterOperator::Ge, "gt" => FilterOperator::Gt, - "in" => FilterOperator::In, - "not_in" => FilterOperator::NotIn, + "is_in" => FilterOperator::IsIn, + "is_not_in" => FilterOperator::IsNotIn, _ => panic!("Unknown filter type in function name"), }; bench_search_edges_by_property_filter::( diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index d02a95e9f6..b3cc6487ee 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -314,37 +314,13 @@ input EdgeAddition { } input EdgeFilter @oneOf { - """ - Source node. - """ - src: NodeFieldFilter - """ - Destination node. - """ - dst: NodeFieldFilter - """ - Property. - """ - property: PropertyFilterExpr - """ - Metadata. - """ - metadata: MetadataFilterExpr - """ - Temporal property. - """ - temporalProperty: TemporalPropertyFilterExpr - """ - AND operator. - """ + src: NodeFieldFilterNew + dst: NodeFieldFilterNew + property: PropertyFilterNew + metadata: PropertyFilterNew + temporalProperty: PropertyFilterNew and: [EdgeFilter!] - """ - OR operator. - """ or: [EdgeFilter!] - """ - NOT operator. - """ not: EdgeFilter } @@ -391,65 +367,20 @@ input EdgeSortBy { } input EdgeViewCollection @oneOf { - """ - Contains only the default layer. - """ defaultLayer: Boolean - """ - Latest time. - """ latest: Boolean - """ - Snapshot at latest time. - """ snapshotLatest: Boolean - """ - Snapshot at specified time. - """ snapshotAt: Int - """ - List of included layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single included layer. - """ layer: String - """ - Single excluded layer. - """ excludeLayer: String - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int } @@ -581,65 +512,20 @@ type Edges { } input EdgesViewCollection @oneOf { - """ - Contains only the default layer. - """ defaultLayer: Boolean - """ - Latest time. - """ latest: Boolean - """ - Snapshot at latest time. - """ snapshotLatest: Boolean - """ - Snapshot at specified time. - """ snapshotAt: Int - """ - List of included layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single included layer. - """ layer: String - """ - Single excluded layer. - """ excludeLayer: String - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int } @@ -897,89 +783,26 @@ enum GraphType { } input GraphViewCollection @oneOf { - """ - Contains only the default layer. - """ defaultLayer: Boolean - """ - List of included layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single included layer. - """ layer: String - """ - Single excluded layer. - """ excludeLayer: String - """ - Subgraph nodes. - """ subgraph: [String!] - """ - Subgraph node types. - """ subgraphNodeTypes: [String!] - """ - List of excluded nodes. - """ excludeNodes: [String!] - """ - Valid state. - """ valid: Boolean - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - View at the latest time. - """ latest: Boolean - """ - Snapshot at specified time. - """ snapshotAt: Int - """ - Snapshot at latest time. - """ snapshotLatest: Boolean - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int - """ - Node filter. - """ nodeFilter: NodeFilter - """ - Edge filter. - """ edgeFilter: EdgeFilter } @@ -1107,25 +930,6 @@ type Metadata { values(keys: [String!]): [Property!]! } -input MetadataFilterExpr { - """ - Node metadata to compare against. - """ - name: String! - """ - Operator. - """ - operator: Operator! - """ - Value. - """ - value: Value - """ - Ops List. - """ - ops: [OpName!] -} - type MutRoot { """ Returns a collection of mutation plugins. @@ -1508,63 +1312,38 @@ input NodeAddition { } enum NodeField { - """ - Node id. - """ NODE_ID - """ - Node name. - """ NODE_NAME - """ - Node type. - """ NODE_TYPE } -input NodeFieldFilter { - """ - Node component to compare against. - """ +input NodeFieldCondition @oneOf { + eq: Value + ne: Value + gt: Value + ge: Value + lt: Value + le: Value + startsWith: Value + endsWith: Value + contains: Value + notContains: Value + isIn: Value + isNotIn: Value +} + +input NodeFieldFilterNew { field: NodeField! - """ - Operator filter. - """ - operator: Operator! - """ - Value filter. - """ - value: Value! + where: NodeFieldCondition! } input NodeFilter @oneOf { - """ - Node filter. - """ - node: NodeFieldFilter - """ - Property filter. - """ - property: PropertyFilterExpr - """ - Metadata filter. - """ - metadata: MetadataFilterExpr - """ - Temporal property filter. - """ - temporalProperty: TemporalPropertyFilterExpr - """ - AND operator. - """ + node: NodeFieldFilterNew + property: PropertyFilterNew + metadata: PropertyFilterNew + temporalProperty: PropertyFilterNew and: [NodeFilter!] - """ - OR operator. - """ or: [NodeFilter!] - """ - NOT operator. - """ not: NodeFilter } @@ -1597,69 +1376,21 @@ input NodeSortBy { } input NodeViewCollection @oneOf { - """ - Contains only the default layer. - """ defaultLayer: Boolean - """ - View at the latest time. - """ latest: Boolean - """ - Snapshot at latest time. - """ snapshotLatest: Boolean - """ - Snapshot at specified time. - """ snapshotAt: Int - """ - List of included layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single included layer. - """ layer: String - """ - Single excluded layer. - """ excludeLayer: String - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int - """ - Node filter. - """ nodeFilter: NodeFilter } @@ -1780,73 +1511,22 @@ type Nodes { } input NodesViewCollection @oneOf { - """ - Contains only the default layer. - """ defaultLayer: Boolean - """ - View at the latest time. - """ latest: Boolean - """ - Snapshot at latest time. - """ snapshotLatest: Boolean - """ - List of included layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single included layer. - """ layer: String - """ - Single excluded layer. - """ excludeLayer: String - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - Snapshot at specified time. - """ snapshotAt: Int - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int - """ - Node filter. - """ nodeFilter: NodeFilter - """ - List of types. - """ typeFilter: [String!] } @@ -1874,71 +1554,6 @@ input ObjectEntry { value: Value! } -enum OpName { - FIRST - LAST - ANY - ALL - LEN - SUM - AVG - MIN - MAX -} - -enum Operator { - """ - Equality operator. - """ - EQUAL - """ - Inequality operator. - """ - NOT_EQUAL - """ - Greater Than Or Equal operator. - """ - GREATER_THAN_OR_EQUAL - """ - Less Than Or Equal operator. - """ - LESS_THAN_OR_EQUAL - """ - Greater Than operator. - """ - GREATER_THAN - """ - Less Than operator. - """ - LESS_THAN - """ - Is None operator. - """ - IS_NONE - """ - Is Some operator. - """ - IS_SOME - """ - Is In operator. - """ - IS_IN - """ - Is Not In operator. - """ - IS_NOT_IN - STARTS_WITH - ENDS_WITH - """ - Contains operator. - """ - CONTAINS - """ - Not Contains operator. - """ - NOT_CONTAINS -} - """ PageRank score. """ @@ -2045,61 +1660,19 @@ type PathFromNode { } input PathFromNodeViewCollection @oneOf { - """ - Latest time. - """ latest: Boolean - """ - Latest snapshot. - """ snapshotLatest: Boolean - """ - Time. - """ snapshotAt: Int - """ - List of layers. - """ layers: [String!] - """ - List of excluded layers. - """ excludeLayers: [String!] - """ - Single layer. - """ layer: String - """ - Single layer to exclude. - """ excludeLayer: String - """ - Window between a start and end time. - """ window: Window - """ - View at a specified time. - """ at: Int - """ - View before a specified time (end exclusive). - """ before: Int - """ - View after a specified time (start exclusive). - """ after: Int - """ - Shrink a Window to a specified start and end time. - """ shrinkWindow: Window - """ - Set the window start to a specified time. - """ shrinkStart: Int - """ - Set the window end to a specified time. - """ shrinkEnd: Int } @@ -2116,6 +1689,36 @@ type PathFromNodeWindowSet { list: [PathFromNode!]! } +input PropCondition @oneOf { + eq: Value + ne: Value + gt: Value + ge: Value + lt: Value + le: Value + startsWith: Value + endsWith: Value + contains: Value + notContains: Value + isIn: Value + isNotIn: Value + isSome: Boolean + isNone: Boolean + between: [Value!] + and: [PropCondition!] + or: [PropCondition!] + not: PropCondition + first: PropCondition + last: PropCondition + any: PropCondition + all: PropCondition + sum: PropCondition + avg: PropCondition + min: PropCondition + max: PropCondition + len: PropCondition +} + type Properties { """ Get property value matching the specified key. @@ -2142,23 +1745,9 @@ type Property { value: PropertyOutput! } -input PropertyFilterExpr { - """ - Node property to compare against. - """ +input PropertyFilterNew { name: String! - """ - Operator. - """ - operator: Operator! - """ - Value. - """ - value: Value - """ - Ops List. - """ - ops: [OpName!] + where: PropCondition! } input PropertyInput { @@ -2316,25 +1905,6 @@ type TemporalProperty { orderedDedupe(latestTime: Boolean!): [PropertyTuple!]! } -input TemporalPropertyFilterExpr { - """ - Name. - """ - name: String! - """ - Operator. - """ - operator: Operator! - """ - Value. - """ - value: Value - """ - Ops List. - """ - ops: [OpName!] -} - input TemporalPropertyInput { """ Time. diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 6e859f7e82..3b51e5c336 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -26,6 +26,14 @@ use std::{ sync::Arc, }; +#[derive(InputObject, Clone, Debug)] +pub struct Window { + /// Window start time. + pub start: i64, + /// Window end time. + pub end: i64, +} + #[derive(OneOfInput, Clone, Debug)] pub enum GraphViewCollection { /// Contains only the default layer. @@ -246,76 +254,155 @@ pub enum PathFromNodeViewCollection { ShrinkEnd(i64), } -#[derive(InputObject, Clone, Debug)] -pub struct Window { - /// Window start time. - pub start: i64, - /// Window end time. - pub end: i64, +#[derive(Enum, Copy, Clone, Debug)] +pub enum NodeField { + /// Node id. + NodeId, + /// Node name. + NodeName, + /// Node type. + NodeType, } -#[derive(Enum, Copy, Clone, Debug)] -pub enum Operator { - /// Equality operator. - Equal, - /// Inequality operator. - NotEqual, - /// Greater Than Or Equal operator. - GreaterThanOrEqual, - /// Less Than Or Equal operator. - LessThanOrEqual, - /// Greater Than operator. - GreaterThan, - /// Less Than operator. - LessThan, - /// Is None operator. - IsNone, - /// Is Some operator. - IsSome, - /// Is In operator. - IsIn, - /// Is Not In operator. - IsNotIn, - StartsWith, - EndsWith, - /// Contains operator. - Contains, - /// Not Contains operator. - NotContains, -} - -impl Display for Operator { +impl Display for NodeField { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let op_str = match self { - Operator::Equal => "EQUAL", - Operator::NotEqual => "NOT_EQUAL", - Operator::GreaterThanOrEqual => "GREATER_THAN_OR_EQUAL", - Operator::LessThanOrEqual => "LESS_THAN_OR_EQUAL", - Operator::GreaterThan => "GREATER_THAN", - Operator::LessThan => "LESS_THAN", - Operator::IsNone => "IS_NONE", - Operator::IsSome => "IS_SOME", - Operator::IsIn => "IS_IN", - Operator::IsNotIn => "IS_NOT_IN", - Operator::StartsWith => "STARTS_WITH", - Operator::EndsWith => "ENDS_WITH", - Operator::Contains => "CONTAINS", - Operator::NotContains => "NOT_CONTAINS", - }; - write!(f, "{op_str}") + write!( + f, + "{}", + match self { + NodeField::NodeId => "node_id", + NodeField::NodeName => "node_name", + NodeField::NodeType => "node_type", + } + ) } } +#[derive(InputObject, Clone, Debug)] +pub struct PropertyFilterNew { + pub name: String, + #[graphql(name = "where")] + pub where_: PropCondition, +} + +#[derive(OneOfInput, Clone, Debug)] +pub enum PropCondition { + Eq(Value), + Ne(Value), + Gt(Value), + Ge(Value), + Lt(Value), + Le(Value), + + StartsWith(Value), + EndsWith(Value), + Contains(Value), + NotContains(Value), + + IsIn(Value), + IsNotIn(Value), + + IsSome(bool), + IsNone(bool), + + And(Vec), + Or(Vec), + Not(Wrapped), + + First(Wrapped), + Last(Wrapped), + Any(Wrapped), + All(Wrapped), + Sum(Wrapped), + Avg(Wrapped), + Min(Wrapped), + Max(Wrapped), + Len(Wrapped), +} + +impl PropCondition { + pub fn op_name(&self) -> Option<&'static str> { + use PropCondition::*; + Some(match self { + Eq(_) => "eq", + Ne(_) => "ne", + Gt(_) => "gt", + Ge(_) => "ge", + Lt(_) => "lt", + Le(_) => "le", + + StartsWith(_) => "startsWith", + EndsWith(_) => "endsWith", + Contains(_) => "contains", + NotContains(_) => "notContains", + + IsIn(_) => "isIn", + IsNotIn(_) => "isNotIn", + + IsSome(_) => "isSome", + IsNone(_) => "isNone", + + And(_) | Or(_) | Not(_) | First(_) | Last(_) | Any(_) | All(_) | Sum(_) | Avg(_) + | Min(_) | Max(_) | Len(_) => return None, + }) + } +} + +#[derive(OneOfInput, Clone, Debug)] +pub enum NodeFieldCondition { + Eq(Value), + Ne(Value), + Gt(Value), + Ge(Value), + Lt(Value), + Le(Value), + + StartsWith(Value), + EndsWith(Value), + Contains(Value), + NotContains(Value), + + IsIn(Value), + IsNotIn(Value), +} + +impl NodeFieldCondition { + pub fn op_name(&self) -> Option<&'static str> { + use NodeFieldCondition::*; + Some(match self { + Eq(_) => "eq", + Ne(_) => "ne", + Gt(_) => "gt", + Ge(_) => "ge", + Lt(_) => "lt", + Le(_) => "le", + StartsWith(_) => "startsWith", + EndsWith(_) => "endsWith", + Contains(_) => "contains", + NotContains(_) => "notContains", + IsIn(_) => "isIn", + IsNotIn(_) => "isNotIn", + }) + } +} + +#[derive(InputObject, Clone, Debug)] +pub struct NodeFieldFilterNew { + pub field: NodeField, + #[graphql(name = "where")] + pub where_: NodeFieldCondition, +} + #[derive(OneOfInput, Clone, Debug)] pub enum NodeFilter { /// Node filter. - Node(NodeFieldFilter), + Node(NodeFieldFilterNew), /// Property filter. - Property(PropertyFilterExpr), + Property(PropertyFilterNew), /// Metadata filter. - Metadata(MetadataFilterExpr), + Metadata(PropertyFilterNew), /// Temporal property filter. - TemporalProperty(TemporalPropertyFilterExpr), + TemporalProperty(PropertyFilterNew), /// AND operator. And(Vec), /// OR operator. @@ -324,12 +411,30 @@ pub enum NodeFilter { Not(Wrapped), } +#[derive(OneOfInput, Clone, Debug)] +pub enum EdgeFilter { + /// Source node filter. + Src(NodeFieldFilterNew), + /// Destination node filter. + Dst(NodeFieldFilterNew), + /// Property filter. + Property(PropertyFilterNew), + /// Metadata filter. + Metadata(PropertyFilterNew), + /// Temporal property filter. + TemporalProperty(PropertyFilterNew), + /// AND operator. + And(Vec), + /// OR operator. + Or(Vec), + /// NOT operator. + Not(Wrapped), +} + #[derive(Clone, Debug)] pub struct Wrapped(Box); - impl Deref for Wrapped { type Target = T; - fn deref(&self) -> &Self::Target { self.0.deref() } @@ -343,10 +448,9 @@ impl Register for Wrapped { impl FromValue for Wrapped { fn from_value(value: async_graphql::Result) -> InputValueResult { - match T::from_value(value) { - Ok(value) => Ok(Wrapped(Box::new(value))), - Err(err) => Err(err.propagate()), - } + T::from_value(value) + .map(|v| Wrapped(Box::new(v))) + .map_err(|e| e.propagate()) } } @@ -355,379 +459,491 @@ impl TypeName for Wrapped { T::get_type_name() } } - impl InputTypeName for Wrapped {} -#[derive(InputObject, Clone, Debug)] -pub struct NodeFieldFilter { - /// Node component to compare against. - pub field: NodeField, - /// Operator filter. - pub operator: Operator, - /// Value filter. - pub value: Value, -} +fn peel_prop_wrappers_and_collect_ops<'a>( + cond: &'a PropCondition, + ops: &mut Vec, +) -> Option<&'a PropCondition> { + use PropCondition::*; -impl NodeFieldFilter { - pub fn validate(&self) -> Result<(), GraphError> { - match self.field { - NodeField::NodeId => validate_id_operator_value_pair(self.operator, &self.value), - _ => validate_operator_value_pair(self.operator, Some(&self.value)), + match cond { + First(inner) => { + ops.push(Op::First); + Some(inner.deref()) + } + Last(inner) => { + ops.push(Op::Last); + Some(inner.deref()) + } + Any(inner) => { + ops.push(Op::Any); + Some(inner.deref()) + } + All(inner) => { + ops.push(Op::All); + Some(inner.deref()) + } + Sum(inner) => { + ops.push(Op::Sum); + Some(inner.deref()) + } + Avg(inner) => { + ops.push(Op::Avg); + Some(inner.deref()) + } + Min(inner) => { + ops.push(Op::Min); + Some(inner.deref()) + } + Max(inner) => { + ops.push(Op::Max); + Some(inner.deref()) + } + Len(inner) => { + ops.push(Op::Len); + Some(inner.deref()) } - } -} - -#[derive(Enum, Copy, Clone, Debug)] -pub enum NodeField { - /// Node id. - NodeId, - /// Node name. - NodeName, - /// Node type. - NodeType, -} -#[derive(OneOfInput, Clone, Debug)] -pub enum EdgeFilter { - /// Source node. - Src(NodeFieldFilter), - /// Destination node. - Dst(NodeFieldFilter), - /// Property. - Property(PropertyFilterExpr), - /// Metadata. - Metadata(MetadataFilterExpr), - /// Temporal property. - TemporalProperty(TemporalPropertyFilterExpr), - /// AND operator. - And(Vec), - /// OR operator. - Or(Vec), - /// NOT operator. - Not(Wrapped), + _ => None, + } } -#[derive(Enum, Debug, Clone, Copy, PartialEq, Eq)] -#[graphql(name = "ListAgg")] -pub enum GqlListAgg { - Len, - Sum, - Avg, - Min, - Max, +fn require_string_value(op: Option<&str>, v: &Value) -> Result { + if let Value::Str(s) = v { + Ok(s.clone()) + } else { + let name = op.unwrap_or("UNKNOWN"); + Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a string value, got {v}" + ))) + } } -#[derive(Enum, Debug, Clone, Copy, PartialEq, Eq)] -#[graphql(name = "ListElemQualifier")] -pub enum GqlListElemQualifier { - Any, - All, +fn require_prop_list_value(op: Option<&str>, v: &Value) -> Result { + if let Value::List(vs) = v { + let props = vs + .iter() + .cloned() + .map(Prop::try_from) + .collect::, _>>()?; + Ok(PropertyFilterValue::Set(Arc::new( + props.into_iter().collect(), + ))) + } else { + let name = op.unwrap_or("UNKNOWN"); + Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a list value, got {v}" + ))) + } } -impl From for Op { - fn from(q: GqlListElemQualifier) -> Self { - match q { - GqlListElemQualifier::Any => Op::Any, - GqlListElemQualifier::All => Op::All, - } +fn require_u64_value(op: Option<&str>, v: &Value) -> Result { + if let Value::U64(i) = v { + Ok(*i) + } else { + let name = op.unwrap_or("UNKNOWN"); + Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a u64 value, got {v}" + ))) } } -impl From for Op { - fn from(a: GqlListAgg) -> Self { - match a { - GqlListAgg::Len => Op::Len, - GqlListAgg::Sum => Op::Sum, - GqlListAgg::Avg => Op::Avg, - GqlListAgg::Min => Op::Min, - GqlListAgg::Max => Op::Max, +fn parse_node_id_scalar(op: Option<&str>, v: &Value) -> Result { + match v { + Value::U64(i) => Ok(FilterValue::ID(GID::U64(*i))), + Value::Str(s) => Ok(FilterValue::ID(GID::Str(s.clone()))), + other => { + let name = op.unwrap_or("UNKNOWN"); + Err(GraphError::InvalidGqlFilter(format!( + "{name} requires int or str, got {other}" + ))) } } } -#[derive(InputObject, Clone, Debug)] -pub struct PropertyFilterExpr { - /// Node property to compare against. - pub name: String, - /// Operator. - pub operator: Operator, - /// Value. - pub value: Option, - /// Ops List. - pub ops: Option>, -} +fn parse_node_id_list(op: Option<&str>, v: &Value) -> Result { + let name = op.unwrap_or("UNKNOWN"); + let Value::List(vs) = v else { + return Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a list value, got {v}" + ))); + }; -impl PropertyFilterExpr { - pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref()) + let all_u64 = vs.iter().all(|v| matches!(v, Value::U64(_))); + let all_str = vs.iter().all(|v| matches!(v, Value::Str(_))); + if !(all_u64 || all_str) { + return Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a homogeneous list of ints or strings" + ))); } -} - -#[derive(InputObject, Clone, Debug)] -pub struct MetadataFilterExpr { - /// Node metadata to compare against. - pub name: String, - /// Operator. - pub operator: Operator, - /// Value. - pub value: Option, - /// Ops List. - pub ops: Option>, -} -impl MetadataFilterExpr { - pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref()) + let mut set = std::collections::HashSet::with_capacity(vs.len()); + if all_u64 { + for v in vs { + if let Value::U64(i) = v { + set.insert(GID::U64(*i)); + } + } + } else { + for v in vs { + if let Value::Str(s) = v { + set.insert(GID::Str(s.clone())); + } + } } + Ok(FilterValue::IDSet(Arc::new(set))) } -#[derive(InputObject, Clone, Debug)] -pub struct TemporalPropertyFilterExpr { - /// Name. - pub name: String, - /// Operator. - pub operator: Operator, - /// Value. - pub value: Option, - /// Ops List. - pub ops: Option>, -} +fn parse_string_list(op: Option<&str>, v: &Value) -> Result { + let name = op.unwrap_or("UNKNOWN"); + let Value::List(vs) = v else { + return Err(GraphError::InvalidGqlFilter(format!( + "{name} requires a list value, got {v}" + ))); + }; -impl TemporalPropertyFilterExpr { - pub fn validate(&self) -> Result<(), GraphError> { - validate_operator_value_pair(self.operator, self.value.as_ref()) - } + let strings = vs + .iter() + .map(|v| { + if let Value::Str(s) = v { + Ok(s.clone()) + } else { + Err(GraphError::InvalidGqlFilter(format!( + "Expected list of strings for {name}, got {v}" + ))) + } + }) + .collect::, _>>()?; + + Ok(FilterValue::Set(Arc::new(strings.into_iter().collect()))) } -#[derive(Enum, Copy, Clone, Debug)] -pub enum OpName { - First, - Last, - Any, - All, - Len, - Sum, - Avg, - Min, - Max, -} - -impl From for Op { - fn from(o: OpName) -> Self { - match o { - OpName::First => Op::First, - OpName::Last => Op::Last, - OpName::Any => Op::Any, - OpName::All => Op::All, - OpName::Len => Op::Len, - OpName::Sum => Op::Sum, - OpName::Avg => Op::Avg, - OpName::Min => Op::Min, - OpName::Max => Op::Max, - } - } +fn translate_node_field_where( + field: NodeField, + cond: &NodeFieldCondition, +) -> Result<(String, FilterValue, FilterOperator), GraphError> { + use FilterOperator as FO; + use NodeField::*; + use NodeFieldCondition::*; + + let field_name = field.to_string(); + let op = cond.op_name(); + + Ok(match (field, cond) { + (NodeId, Eq(v)) => (field_name, parse_node_id_scalar(op, v)?, FO::Eq), + (NodeId, Ne(v)) => (field_name, parse_node_id_scalar(op, v)?, FO::Ne), + (NodeId, Gt(v)) => ( + field_name, + FilterValue::ID(GID::U64(require_u64_value(op, v)?)), + FO::Gt, + ), + (NodeId, Ge(v)) => ( + field_name, + FilterValue::ID(GID::U64(require_u64_value(op, v)?)), + FO::Ge, + ), + (NodeId, Lt(v)) => ( + field_name, + FilterValue::ID(GID::U64(require_u64_value(op, v)?)), + FO::Lt, + ), + (NodeId, Le(v)) => ( + field_name, + FilterValue::ID(GID::U64(require_u64_value(op, v)?)), + FO::Le, + ), + + (NodeId, StartsWith(v)) => ( + field_name, + FilterValue::ID(GID::Str(require_string_value(op, v)?)), + FO::StartsWith, + ), + (NodeId, EndsWith(v)) => ( + field_name, + FilterValue::ID(GID::Str(require_string_value(op, v)?)), + FO::EndsWith, + ), + (NodeId, Contains(v)) => ( + field_name, + FilterValue::ID(GID::Str(require_string_value(op, v)?)), + FO::Contains, + ), + (NodeId, NotContains(v)) => ( + field_name, + FilterValue::ID(GID::Str(require_string_value(op, v)?)), + FO::NotContains, + ), + + (NodeId, IsIn(v)) => (field_name, parse_node_id_list(op, v)?, FO::IsIn), + (NodeId, IsNotIn(v)) => (field_name, parse_node_id_list(op, v)?, FO::IsNotIn), + + (NodeName, Eq(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Eq, + ), + (NodeName, Ne(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Ne, + ), + (NodeName, Gt(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Gt, + ), + (NodeName, Ge(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Ge, + ), + (NodeName, Lt(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Lt, + ), + (NodeName, Le(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Le, + ), + + (NodeName, StartsWith(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::StartsWith, + ), + (NodeName, EndsWith(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::EndsWith, + ), + (NodeName, Contains(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Contains, + ), + (NodeName, NotContains(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::NotContains, + ), + + (NodeName, IsIn(v)) => (field_name, parse_string_list(op, v)?, FO::IsIn), + (NodeName, IsNotIn(v)) => (field_name, parse_string_list(op, v)?, FO::IsNotIn), + + (NodeType, Eq(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Eq, + ), + (NodeType, Ne(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Ne, + ), + (NodeType, Gt(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Gt, + ), + (NodeType, Ge(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Ge, + ), + (NodeType, Lt(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Lt, + ), + (NodeType, Le(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Le, + ), + + (NodeType, StartsWith(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::StartsWith, + ), + (NodeType, EndsWith(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::EndsWith, + ), + (NodeType, Contains(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::Contains, + ), + (NodeType, NotContains(v)) => ( + field_name, + FilterValue::Single(require_string_value(op, v)?), + FO::NotContains, + ), + + (NodeType, IsIn(v)) => (field_name, parse_string_list(op, v)?, FO::IsIn), + (NodeType, IsNotIn(v)) => (field_name, parse_string_list(op, v)?, FO::IsNotIn), + }) } -fn field_value(value: Value, operator: Operator) -> Result { - let prop = Prop::try_from(value.clone())?; - match (prop, operator) { - (Prop::List(list), Operator::IsIn | Operator::IsNotIn) => { - let strings: Vec = list - .iter() - .map(|p| match p { - Prop::Str(s) => Ok(s.to_string()), - _ => Err(GraphError::InvalidGqlFilter(format!( - "Invalid field value {:?} or operator {}", - value, operator - ))), - }) - .collect::>()?; - - Ok(FilterValue::Set(Arc::new( - strings.iter().cloned().collect(), - ))) +fn translate_prop_leaf_to_filter( + name_for_errors: &str, + cmp: &PropCondition, +) -> Result<(FilterOperator, PropertyFilterValue), GraphError> { + use FilterOperator as FO; + use PropCondition::*; + + let single = |v: &Value| -> Result { + Ok(PropertyFilterValue::Single(Prop::try_from(v.clone())?)) + }; + + Ok(match cmp { + Eq(v) => (FO::Eq, single(v)?), + Ne(v) => (FO::Ne, single(v)?), + Gt(v) => (FO::Gt, single(v)?), + Ge(v) => (FO::Ge, single(v)?), + Lt(v) => (FO::Lt, single(v)?), + Le(v) => (FO::Le, single(v)?), + + StartsWith(v) => ( + FO::StartsWith, + PropertyFilterValue::Single(Prop::Str(require_string_value(cmp.op_name(), v)?.into())), + ), + EndsWith(v) => ( + FO::EndsWith, + PropertyFilterValue::Single(Prop::Str(require_string_value(cmp.op_name(), v)?.into())), + ), + + Contains(v) => (FO::Contains, single(v)?), + NotContains(v) => (FO::NotContains, single(v)?), + + IsIn(v) => (FO::IsIn, require_prop_list_value(cmp.op_name(), v)?), + IsNotIn(v) => (FO::IsNotIn, require_prop_list_value(cmp.op_name(), v)?), + + IsSome(true) => (FO::IsSome, PropertyFilterValue::None), + IsNone(true) => (FO::IsNone, PropertyFilterValue::None), + + And(_) | Or(_) | Not(_) | First(_) | Last(_) | Any(_) | All(_) | Sum(_) + | Avg(_) | Min(_) | Max(_) | Len(_) | IsSome(false) | IsNone(false) => { + return Err(GraphError::InvalidGqlFilter(format!( + "Expected comparison at leaf for {}", + name_for_errors + ))); } - (Prop::Str(p), _) => Ok(FilterValue::Single(p.to_string())), - _ => Err(GraphError::InvalidGqlFilter(format!( - "Invalid field value {:?} or operator {}", - value, operator - ))), - } + }) } -fn node_field_value( - field: NodeField, - value: Value, - operator: Operator, -) -> Result { - match field { - NodeField::NodeId => id_field_value(value, operator), - NodeField::NodeName | NodeField::NodeType => string_field_value(value, operator), +fn build_property_filter_from_condition( + prop_ref: PropertyRef, + cond: &PropCondition, +) -> Result, GraphError> { + let mut ops: Vec = Vec::new(); + let mut cursor = cond; + while let Some(inner) = peel_prop_wrappers_and_collect_ops(cursor, &mut ops) { + cursor = inner; } + let (operator, prop_value) = translate_prop_leaf_to_filter(prop_ref.name(), cursor)?; + Ok(PropertyFilter { + prop_ref, + prop_value, + operator, + ops, + _phantom: PhantomData, + }) } -fn id_field_value(value: Value, operator: Operator) -> Result { - use Operator::*; - - match operator { - Equal | NotEqual => match value { - Value::U64(i) => { - let u = i - .try_into() - .map_err(|_| GraphError::InvalidGqlFilter("node_id must be >= 0".into()))?; - Ok(FilterValue::ID(GID::U64(u))) - } - Value::Str(s) => Ok(FilterValue::ID(GID::Str(s))), - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires int or string, got {v}" - ))), - }, - - GreaterThan | GreaterThanOrEqual | LessThan | LessThanOrEqual => match value { - Value::U64(i) => { - let u = i - .try_into() - .map_err(|_| GraphError::InvalidGqlFilter("node_id must be >= 0".into()))?; - Ok(FilterValue::ID(GID::U64(u))) +fn build_node_filter_from_prop_condition( + prop_ref: PropertyRef, + cond: &PropCondition, +) -> Result { + use PropCondition::*; + + match cond { + And(list) => { + let mut it = list.iter(); + let first = it + .next() + .ok_or_else(|| GraphError::InvalidGqlFilter("and expects non-empty list".into()))?; + let mut acc = build_node_filter_from_prop_condition(prop_ref.clone(), first)?; + for c in it { + let next = build_node_filter_from_prop_condition(prop_ref.clone(), c)?; + acc = CompositeNodeFilter::And(Box::new(acc), Box::new(next)); } - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires an integer (u64) value, got {v}" - ))), - }, - - StartsWith | EndsWith | Contains | NotContains => match value { - Value::Str(s) => Ok(FilterValue::ID(GID::Str(s))), - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires a string value, got {v}" - ))), - }, - - IsIn | IsNotIn => match value { - Value::List(items) => { - let all_u64 = items.iter().all(|v| matches!(v, Value::U64(_))); - let all_str = items.iter().all(|v| matches!(v, Value::Str(_))); - - if !(all_u64 || all_str) { - return Err(GraphError::InvalidGqlFilter( - "Operator {operator} on node_id requires a homogeneous list of ints or strings".into(), - )); - } - - let mut set = std::collections::HashSet::with_capacity(items.len()); - if all_u64 { - for v in items { - if let Value::U64(i) = v { - let u = i.try_into().map_err(|_| { - GraphError::InvalidGqlFilter("node_id must be >= 0".into()) - })?; - set.insert(GID::U64(u)); - } - } - } else { - for v in items { - if let Value::Str(s) = v { - set.insert(GID::Str(s)); - } - } - } - Ok(FilterValue::IDSet(Arc::new(set))) + Ok(acc) + } + Or(list) => { + let mut it = list.iter(); + let first = it + .next() + .ok_or_else(|| GraphError::InvalidGqlFilter("or expects non-empty list".into()))?; + let mut acc = build_node_filter_from_prop_condition(prop_ref.clone(), first)?; + for c in it { + let next = build_node_filter_from_prop_condition(prop_ref.clone(), c)?; + acc = CompositeNodeFilter::Or(Box::new(acc), Box::new(next)); } - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires a list, got {v}" - ))), - }, - - IsSome | IsNone => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} is not supported on node_id" - ))), - } -} - -fn string_field_value(value: Value, operator: Operator) -> Result { - use Operator::*; - match (value, operator) { - ( - Value::Str(s), - Equal | NotEqual | StartsWith | EndsWith | Contains | NotContains | GreaterThan - | GreaterThanOrEqual | LessThan | LessThanOrEqual, - ) => Ok(FilterValue::Single(s)), - (Value::List(items), IsIn | IsNotIn) => { - let strings = items - .into_iter() - .map(|v| match v { - Value::Str(s) => Ok(s), - other => Err(GraphError::InvalidGqlFilter(format!( - "Expected list of strings, got {other}" - ))), - }) - .collect::, _>>()?; - - Ok(FilterValue::Set(Arc::new(strings.into_iter().collect()))) + Ok(acc) + } + Not(inner) => { + let nf = build_node_filter_from_prop_condition(prop_ref, inner)?; + Ok(CompositeNodeFilter::Not(Box::new(nf))) + } + _ => { + let pf = build_property_filter_from_condition::< + raphtory::db::graph::views::filter::model::node_filter::NodeFilter, + >(prop_ref, cond)?; + Ok(CompositeNodeFilter::Property(pf)) } - (v, op) => Err(GraphError::InvalidGqlFilter(format!( - "Invalid value/operator combination for string field: value {v:?}, operator {op}" - ))), } } impl TryFrom for CompositeNodeFilter { type Error = GraphError; - fn try_from(filter: NodeFilter) -> Result { match filter { NodeFilter::Node(node) => { - node.validate()?; + let (field_name, field_value, operator) = + translate_node_field_where(node.field, &node.where_)?; Ok(CompositeNodeFilter::Node(Filter { - field_name: node.field.to_string(), - field_value: node_field_value(node.field, node.value, node.operator)?, - operator: node.operator.into(), + field_name, + field_value, + operator, })) } NodeFilter::Property(prop) => { - prop.validate()?; - Ok(CompositeNodeFilter::Property(prop.try_into()?)) + let prop_ref = PropertyRef::Property(prop.name); + build_node_filter_from_prop_condition(prop_ref, &prop.where_) } NodeFilter::Metadata(prop) => { - prop.validate()?; - Ok(CompositeNodeFilter::Property(prop.try_into()?)) + let prop_ref = PropertyRef::Metadata(prop.name); + build_node_filter_from_prop_condition(prop_ref, &prop.where_) } NodeFilter::TemporalProperty(prop) => { - prop.validate()?; - Ok(CompositeNodeFilter::Property(prop.try_into()?)) + let prop_ref = PropertyRef::TemporalProperty(prop.name); + build_node_filter_from_prop_condition(prop_ref, &prop.where_) } NodeFilter::And(and_filters) => { - let mut iter = and_filters - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()? - .into_iter(); - if let Some(first) = iter.next() { - let and_chain = iter.fold(first, |acc, next| { - CompositeNodeFilter::And(Box::new(acc), Box::new(next)) - }); - Ok(and_chain) - } else { - Err(GraphError::InvalidGqlFilter( - "Filter 'and' requires non-empty list".to_string(), - )) - } + let mut iter = and_filters.into_iter().map(TryInto::try_into); + let first = iter.next().ok_or_else(|| { + GraphError::InvalidGqlFilter("Filter 'and' requires non-empty list".into()) + })??; + Ok(iter.try_fold(first, |acc, next| { + let n = next?; + Ok::<_, GraphError>(CompositeNodeFilter::And(Box::new(acc), Box::new(n))) + })?) } NodeFilter::Or(or_filters) => { - let mut iter = or_filters - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()? - .into_iter(); - if let Some(first) = iter.next() { - let or_chain = iter.fold(first, |acc, next| { - CompositeNodeFilter::Or(Box::new(acc), Box::new(next)) - }); - Ok(or_chain) - } else { - Err(GraphError::InvalidGqlFilter( - "Filter 'or' requires non-empty list".to_string(), - )) - } + let mut iter = or_filters.into_iter().map(TryInto::try_into); + let first = iter.next().ok_or_else(|| { + GraphError::InvalidGqlFilter("Filter 'or' requires non-empty list".into()) + })??; + Ok(iter.try_fold(first, |acc, next| { + let n = next?; + Ok::<_, GraphError>(CompositeNodeFilter::Or(Box::new(acc), Box::new(n))) + })?) } NodeFilter::Not(not_filters) => { let inner = CompositeNodeFilter::try_from(not_filters.deref().clone())?; @@ -737,74 +953,111 @@ impl TryFrom for CompositeNodeFilter { } } +fn build_edge_filter_from_prop_condition( + prop_ref: PropertyRef, + cond: &PropCondition, +) -> Result { + use PropCondition::*; + + match cond { + And(list) => { + let mut it = list.iter(); + let first = it + .next() + .ok_or_else(|| GraphError::InvalidGqlFilter("and expects non-empty list".into()))?; + let mut acc = build_edge_filter_from_prop_condition(prop_ref.clone(), first)?; + for c in it { + let next = build_edge_filter_from_prop_condition(prop_ref.clone(), c)?; + acc = CompositeEdgeFilter::And(Box::new(acc), Box::new(next)); + } + Ok(acc) + } + Or(list) => { + let mut it = list.iter(); + let first = it + .next() + .ok_or_else(|| GraphError::InvalidGqlFilter("or expects non-empty list".into()))?; + let mut acc = build_edge_filter_from_prop_condition(prop_ref.clone(), first)?; + for c in it { + let next = build_edge_filter_from_prop_condition(prop_ref.clone(), c)?; + acc = CompositeEdgeFilter::Or(Box::new(acc), Box::new(next)); + } + Ok(acc) + } + Not(inner) => { + let ef = build_edge_filter_from_prop_condition(prop_ref, inner)?; + Ok(CompositeEdgeFilter::Not(Box::new(ef))) + } + _ => { + let pf = build_property_filter_from_condition::< + raphtory::db::graph::views::filter::model::edge_filter::EdgeFilter, + >(prop_ref, cond)?; + Ok(CompositeEdgeFilter::Property(pf)) + } + } +} + impl TryFrom for CompositeEdgeFilter { type Error = GraphError; - fn try_from(filter: EdgeFilter) -> Result { match filter { EdgeFilter::Src(src) => { - src.validate()?; + 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: node_field_value(src.field, src.value, src.operator)?, - operator: src.operator.into(), + field_value, + operator, })) } EdgeFilter::Dst(dst) => { - dst.validate()?; + 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: node_field_value(dst.field, dst.value, dst.operator)?, - operator: dst.operator.into(), + field_value, + operator, })) } - EdgeFilter::Property(prop) => { - prop.validate()?; - Ok(CompositeEdgeFilter::Property(prop.try_into()?)) + EdgeFilter::Property(p) => { + let prop_ref = PropertyRef::Property(p.name); + build_edge_filter_from_prop_condition(prop_ref, &p.where_) } - EdgeFilter::Metadata(prop) => { - prop.validate()?; - Ok(CompositeEdgeFilter::Property(prop.try_into()?)) + EdgeFilter::Metadata(p) => { + let prop_ref = PropertyRef::Metadata(p.name); + build_edge_filter_from_prop_condition(prop_ref, &p.where_) } - EdgeFilter::TemporalProperty(prop) => { - prop.validate()?; - Ok(CompositeEdgeFilter::Property(prop.try_into()?)) + EdgeFilter::TemporalProperty(p) => { + let prop_ref = PropertyRef::TemporalProperty(p.name); + build_edge_filter_from_prop_condition(prop_ref, &p.where_) } EdgeFilter::And(and_filters) => { - let mut iter = and_filters - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()? - .into_iter(); - - if let Some(first) = iter.next() { - let and_chain = iter.fold(first, |acc, next| { - CompositeEdgeFilter::And(Box::new(acc), Box::new(next)) - }); - Ok(and_chain) - } else { - Err(GraphError::InvalidGqlFilter( - "Filter 'and' requires non-empty list".to_string(), - )) - } + let mut iter = and_filters.into_iter().map(TryInto::try_into); + let first = iter.next().ok_or_else(|| { + GraphError::InvalidGqlFilter("Filter 'and' requires non-empty list".into()) + })??; + Ok(iter.try_fold(first, |acc, next| { + let n = next?; + Ok::<_, GraphError>(CompositeEdgeFilter::And(Box::new(acc), Box::new(n))) + })?) } EdgeFilter::Or(or_filters) => { - let mut iter = or_filters - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()? - .into_iter(); - - if let Some(first) = iter.next() { - let or_chain = iter.fold(first, |acc, next| { - CompositeEdgeFilter::Or(Box::new(acc), Box::new(next)) - }); - Ok(or_chain) - } else { - Err(GraphError::InvalidGqlFilter( - "Filter 'or' requires non-empty list".to_string(), - )) - } + let mut iter = or_filters.into_iter().map(TryInto::try_into); + let first = iter.next().ok_or_else(|| { + GraphError::InvalidGqlFilter("Filter 'or' requires non-empty list".into()) + })??; + Ok(iter.try_fold(first, |acc, next| { + let n = next?; + Ok::<_, GraphError>(CompositeEdgeFilter::Or(Box::new(acc), Box::new(n))) + })?) } EdgeFilter::Not(not_filters) => { let inner = CompositeEdgeFilter::try_from(not_filters.deref().clone())?; @@ -813,206 +1066,3 @@ impl TryFrom for CompositeEdgeFilter { } } } - -fn build_property_filter( - prop_ref: PropertyRef, - operator: Operator, - value: Option<&Value>, - ops: Vec, -) -> Result, GraphError> { - let prop = value.cloned().map(Prop::try_from).transpose()?; - - validate_operator_value_pair(operator, value)?; - - let prop_value = match (&prop, operator) { - (Some(Prop::List(list)), Operator::IsIn | Operator::IsNotIn) => { - PropertyFilterValue::Set(Arc::new(list.iter().cloned().collect())) - } - (Some(p), _) => PropertyFilterValue::Single(p.clone()), - (None, _) => PropertyFilterValue::None, - }; - - Ok(PropertyFilter { - prop_ref, - prop_value, - operator: operator.into(), - ops, - _phantom: PhantomData, - }) -} - -impl TryFrom for PropertyFilter { - type Error = GraphError; - - fn try_from(expr: PropertyFilterExpr) -> Result { - expr.validate()?; - - let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); - - build_property_filter( - PropertyRef::Property(expr.name), - expr.operator, - expr.value.as_ref(), - ops, - ) - } -} - -impl TryFrom for PropertyFilter { - type Error = GraphError; - - fn try_from(expr: MetadataFilterExpr) -> Result { - expr.validate()?; - - let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); - - build_property_filter( - PropertyRef::Metadata(expr.name), - expr.operator, - expr.value.as_ref(), - ops, - ) - } -} - -impl TryFrom for PropertyFilter { - type Error = GraphError; - - fn try_from(expr: TemporalPropertyFilterExpr) -> Result { - expr.validate()?; - - let ops: Vec<_> = expr.ops.into_iter().flatten().map(Into::into).collect(); - - build_property_filter( - PropertyRef::TemporalProperty(expr.name), - expr.operator, - expr.value.as_ref(), - ops, - ) - } -} - -impl Display for NodeField { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let field_name = match self { - NodeField::NodeId => "node_id", - NodeField::NodeName => "node_name", - NodeField::NodeType => "node_type", - }; - write!(f, "{}", field_name) - } -} - -impl From for FilterOperator { - fn from(op: Operator) -> Self { - match op { - Operator::Equal => FilterOperator::Eq, - Operator::NotEqual => FilterOperator::Ne, - Operator::GreaterThanOrEqual => FilterOperator::Ge, - Operator::LessThanOrEqual => FilterOperator::Le, - Operator::GreaterThan => FilterOperator::Gt, - Operator::LessThan => FilterOperator::Lt, - Operator::IsIn => FilterOperator::In, - Operator::IsNotIn => FilterOperator::NotIn, - Operator::IsSome => FilterOperator::IsSome, - Operator::IsNone => FilterOperator::IsNone, - Operator::StartsWith => FilterOperator::StartsWith, - Operator::EndsWith => FilterOperator::EndsWith, - Operator::Contains => FilterOperator::Contains, - Operator::NotContains => FilterOperator::NotContains, - } - } -} - -fn validate_id_operator_value_pair(operator: Operator, value: &Value) -> Result<(), GraphError> { - use Operator::*; - - match operator { - Equal | NotEqual => match value { - Value::U64(_) | Value::Str(_) => Ok(()), - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires int or string, got {v}" - ))), - }, - GreaterThan | GreaterThanOrEqual | LessThan | LessThanOrEqual => match value { - Value::U64(_) => Ok(()), - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires an integer (u64) value, got {v}" - ))), - }, - StartsWith | EndsWith | Contains | NotContains => match value { - Value::Str(_) => Ok(()), - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires a string value, got {v}" - ))), - }, - IsIn | IsNotIn => match value { - Value::List(items) => { - let all_u64 = items.iter().all(|v| matches!(v, Value::U64(_))); - let all_str = items.iter().all(|v| matches!(v, Value::Str(_))); - if all_u64 || all_str { - Ok(()) - } else { - Err(GraphError::InvalidGqlFilter( - format!("Operator {operator} on node_id requires a homogeneous list of ints or strings"), - )) - } - } - v => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} on node_id requires a list, got {v}" - ))), - }, - IsNone | IsSome => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} is not supported on node_id" - ))), - } -} - -fn validate_operator_value_pair( - operator: Operator, - value: Option<&Value>, -) -> Result<(), GraphError> { - use Operator::*; - - match operator { - IsSome | IsNone => { - if value.is_some() { - Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} does not accept a value" - ))) - } else { - Ok(()) - } - } - - IsIn | IsNotIn => match value { - Some(Value::List(_)) => Ok(()), - Some(v) => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} requires a list value, got {v}" - ))), - None => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} requires a list" - ))), - }, - - StartsWith | EndsWith | Contains | NotContains => match value { - Some(Value::Str(_)) => Ok(()), - Some(v) => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} requires a string value, got {v}" - ))), - None => Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} requires a string value" - ))), - }, - - Equal | NotEqual | LessThan | LessThanOrEqual | GreaterThan | GreaterThanOrEqual => { - if value.is_none() { - return Err(GraphError::InvalidGqlFilter(format!( - "Operator {operator} requires a value" - ))); - } - - Ok(()) - } - } -} diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 8c89360c91..d0ee6d4f5c 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -253,7 +253,7 @@ mod test_composite_filters { ); assert_eq!( - "((((node_type NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IN [2, 10])) OR (node_name == pometry OR p5 == 9))", + "((((node_type IS_NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IS_IN [2, 10])) OR (node_name == pometry OR p5 == 9))", CompositeNodeFilter::Or(Box::new(CompositeNodeFilter::And( Box::new(CompositeNodeFilter::And( Box::new(CompositeNodeFilter::And( @@ -321,7 +321,7 @@ mod test_composite_filters { ); assert_eq!( - "((((edge_type NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IN [2, 10])) OR (src == pometry OR p5 == 9))", + "((((edge_type IS_NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IS_IN [2, 10])) OR (src == pometry OR p5 == 9))", CompositeEdgeFilter::Or( Box::new(CompositeEdgeFilter::And( Box::new(CompositeEdgeFilter::And( diff --git a/raphtory/src/db/graph/views/filter/model/filter_operator.rs b/raphtory/src/db/graph/views/filter/model/filter_operator.rs index eb77a11999..a70c919064 100644 --- a/raphtory/src/db/graph/views/filter/model/filter_operator.rs +++ b/raphtory/src/db/graph/views/filter/model/filter_operator.rs @@ -11,8 +11,8 @@ pub enum FilterOperator { Le, Gt, Ge, - In, - NotIn, + IsIn, + IsNotIn, IsSome, IsNone, StartsWith, @@ -34,8 +34,8 @@ impl Display for FilterOperator { FilterOperator::Le => "<=", FilterOperator::Gt => ">", FilterOperator::Ge => ">=", - FilterOperator::In => "IN", - FilterOperator::NotIn => "NOT_IN", + FilterOperator::IsIn => "IS_IN", + FilterOperator::IsNotIn => "IS_NOT_IN", FilterOperator::IsSome => "IS_SOME", FilterOperator::IsNone => "IS_NONE", FilterOperator::StartsWith => "STARTS_WITH", @@ -106,8 +106,8 @@ impl FilterOperator { T: Eq + std::hash::Hash, { match self { - FilterOperator::In => |set: &HashSet, value: &T| set.contains(value), - FilterOperator::NotIn => |set: &HashSet, value: &T| !set.contains(value), + FilterOperator::IsIn => |set: &HashSet, value: &T| set.contains(value), + FilterOperator::IsNotIn => |set: &HashSet, value: &T| !set.contains(value), _ => panic!("Collection operation not supported for this operator"), } } @@ -186,18 +186,18 @@ impl FilterOperator { } } - In | NotIn | IsSome | IsNone => false, + IsIn | IsNotIn | IsSome | IsNone => false, }, Set(set) => match self { - In => { + IsIn => { if let Some(r) = right { set.contains(r) } else { false } } - NotIn => { + IsNotIn => { if let Some(r) = right { !set.contains(r) } else { @@ -231,9 +231,9 @@ impl FilterOperator { }, FilterValue::Set(l) => match self { - FilterOperator::In | FilterOperator::NotIn => match right { + FilterOperator::IsIn | FilterOperator::IsNotIn => match right { Some(r) => self.collection_operation()(l, &r.to_string()), - None => matches!(self, FilterOperator::NotIn), + None => matches!(self, FilterOperator::IsNotIn), }, _ => unreachable!(), }, @@ -278,13 +278,13 @@ impl FilterOperator { FilterValue::IDSet(set) => match right { GidRef::U64(r) => match self { - FilterOperator::In => set.contains(&GID::U64(r)), - FilterOperator::NotIn => !set.contains(&GID::U64(r)), + FilterOperator::IsIn => set.contains(&GID::U64(r)), + FilterOperator::IsNotIn => !set.contains(&GID::U64(r)), _ => false, }, GidRef::Str(s) => match self { - FilterOperator::In => set.contains(&GID::Str(s.to_string())), - FilterOperator::NotIn => !set.contains(&GID::Str(s.to_string())), + FilterOperator::IsIn => set.contains(&GID::Str(s.to_string())), + FilterOperator::IsNotIn => !set.contains(&GID::Str(s.to_string())), _ => false, }, }, @@ -292,8 +292,8 @@ impl FilterOperator { FilterValue::Set(set) => match right { GidRef::U64(_) => false, GidRef::Str(s) => match self { - FilterOperator::In => set.contains(s), - FilterOperator::NotIn => !set.contains(s), + FilterOperator::IsIn => set.contains(s), + FilterOperator::IsNotIn => !set.contains(s), _ => false, }, }, diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index e16192c55b..3d934244c8 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -95,7 +95,7 @@ impl Filter { Self { field_name: field_name.into(), field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), - operator: FilterOperator::In, + operator: FilterOperator::IsIn, } } @@ -106,7 +106,7 @@ impl Filter { Self { field_name: field_name.into(), field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), - operator: FilterOperator::NotIn, + operator: FilterOperator::IsNotIn, } } @@ -183,7 +183,7 @@ impl Filter { Self { field_name: field_name.into(), field_value: FilterValue::IDSet(Arc::new(set)), - operator: FilterOperator::In, + operator: FilterOperator::IsIn, } } @@ -196,7 +196,7 @@ impl Filter { Self { field_name: field_name.into(), field_value: FilterValue::IDSet(Arc::new(set)), - operator: FilterOperator::NotIn, + operator: FilterOperator::IsNotIn, } } @@ -262,7 +262,7 @@ impl Filter { } else { // No endpoint node -> no value present. match self.operator { - FilterOperator::Ne | FilterOperator::NotIn => true, + FilterOperator::Ne | FilterOperator::IsNotIn => true, _ => false, } } 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 eae156eb48..2c2326d9db 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -349,7 +349,10 @@ impl NodeFilter { }; let op_allowed = match kind { - U64 => matches!(filter.operator, Eq | Ne | Lt | Le | Gt | Ge | In | NotIn), + U64 => matches!( + filter.operator, + Eq | Ne | Lt | Le | Gt | Ge | IsIn | IsNotIn + ), Str => matches!( filter.operator, Eq | Ne @@ -358,8 +361,8 @@ impl NodeFilter { | Contains | NotContains | FuzzySearch { .. } - | In - | NotIn + | IsIn + | IsNotIn ), }; @@ -379,7 +382,7 @@ impl NodeFilter { } match filter.operator { - In | NotIn => { + IsIn | IsNotIn => { if !matches!( filter.field_value, FilterValue::IDSet(_) | FilterValue::Set(_) 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 bab1ed6aaa..6c791696b7 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -287,7 +287,7 @@ impl PropertyFilter { Self { prop_ref, prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), - operator: FilterOperator::In, + operator: FilterOperator::IsIn, ops: vec![], _phantom: PhantomData, } @@ -297,7 +297,7 @@ impl PropertyFilter { Self { prop_ref, prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), - operator: FilterOperator::NotIn, + operator: FilterOperator::IsNotIn, ops: vec![], _phantom: PhantomData, } @@ -471,7 +471,7 @@ impl PropertyFilter { return Err(GraphError::InvalidFilterCmp(fd)); } } - FilterOperator::In | FilterOperator::NotIn => match &self.prop_value { + FilterOperator::IsIn | FilterOperator::IsNotIn => match &self.prop_value { PropertyFilterValue::Set(_) => {} PropertyFilterValue::None => { return Err(GraphError::InvalidFilterExpectSetGotNone(self.operator)) diff --git a/raphtory/src/search/query_builder.rs b/raphtory/src/search/query_builder.rs index 8d1b0a5d0e..6013930e49 100644 --- a/raphtory/src/search/query_builder.rs +++ b/raphtory/src/search/query_builder.rs @@ -114,8 +114,8 @@ impl<'a> QueryBuilder<'a> { .collect(); let terms = terms?; match &filter.operator { - FilterOperator::In => create_in_query(terms), - FilterOperator::NotIn => create_not_in_query(terms), + FilterOperator::IsIn => create_in_query(terms), + FilterOperator::IsNotIn => create_not_in_query(terms), _ => unreachable!(), } } @@ -178,8 +178,8 @@ impl<'a> QueryBuilder<'a> { .collect(); let terms = terms?; match operator { - FilterOperator::In => create_in_query(terms), - FilterOperator::NotIn => create_not_in_query(terms), + FilterOperator::IsIn => create_in_query(terms), + FilterOperator::IsNotIn => create_not_in_query(terms), _ => unreachable!(), } } @@ -242,8 +242,8 @@ impl<'a> QueryBuilder<'a> { .collect(); let terms = terms?; match operator { - FilterOperator::In => create_in_query(terms), - FilterOperator::NotIn => create_not_in_query(terms), + FilterOperator::IsIn => create_in_query(terms), + FilterOperator::IsNotIn => create_not_in_query(terms), _ => unreachable!(), } } From 560fc182ae15e99d373b1378b95a2a8c6357a128 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:35:38 +0100 Subject: [PATCH 07/42] impl nodes select filtering in gql --- .../test_filters/test_node_filter_gql.py | 32 ++ .../test_nodes_property_filter.py | 116 ++++++ raphtory-graphql/schema.graphql | 354 +++++++++++++++++- raphtory-graphql/src/model/graph/filtering.rs | 18 +- raphtory-graphql/src/model/graph/graph.rs | 30 +- raphtory-graphql/src/model/graph/nodes.rs | 10 + 6 files changed, 542 insertions(+), 18 deletions(-) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py index c0e9737024..e361127af2 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py @@ -81,3 +81,35 @@ def test_filter_nodes_with_num_ids_for_node_id_eq_gql(graph): """ expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "1"}]}}}} run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_chained_selection_with_node_filter_by_node_and_prop_filter(graph): + query = """ + query { + graph(path: "g") { + nodes { + select(filter: { node: { + field: NODE_TYPE + where: { eq: { str: "fire_nation" } } + } }) { + select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + nodeFilter(filter:{ + property: { name: "p100", where: { gt: { i64: 30 } } } + }) { + list { + name + } + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": {"select": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + } + } + run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index 54923bb4e9..0e1605220d 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -5,6 +5,7 @@ create_test_graph2, create_test_graph3, init_graph, + init_graph2, ) from utils import run_graphql_test, run_graphql_error_test @@ -805,3 +806,118 @@ def test_nodes_temporal_property_filter_any_avg(graph): "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) + + +EVENT_GRAPH = init_graph2(Graph()) +PERSISTENT_GRAPH = init_graph2(PersistentGraph()) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_selection_by_prop_filter(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + list { + neighbours { + list { + name + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": { + "list": [ + {"neighbours": {"list": [{"name": "2"}, {"name": "3"}]}}, + { + "neighbours": { + "list": [{"name": "1"}, {"name": "2"}, {"name": "4"}] + } + }, + ] + } + } + } + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_selection_with_node_filter_by_prop_filter(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + nodeFilter(filter:{ + property: { name: "p100", where: { gt: { i64: 30 } } } + }) { + list { + name + } + } + } + } + } + """ + expected_output = { + "graph": {"nodes": {"nodeFilter": {"list": [{"name": "1"}, {"name": "3"}]}}} + } + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_chained_selection_with_node_filter_by_prop_filter(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + nodeFilter(filter:{ + property: { name: "p100", where: { gt: { i64: 30 } } } + }) { + list { + name + } + } + } + } + } + } + """ + expected_output = { + "graph": {"nodes": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + } + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_chained_selection_with_node_filter_by_prop_filter_ver2(graph): + query = """ + query { + graph(path: "g") { + nodes { + select(filter: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + nodeFilter(filter:{ + property: { name: "p100", where: { gt: { i64: 30 } } } + }) { + list { + name + } + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": {"select": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + } + } + run_graphql_test(query, expected_output, graph) diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index b3cc6487ee..5160973635 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -314,13 +314,37 @@ input EdgeAddition { } input EdgeFilter @oneOf { + """ + Source node filter. + """ src: NodeFieldFilterNew + """ + Destination node filter. + """ dst: NodeFieldFilterNew + """ + Property filter. + """ property: PropertyFilterNew + """ + Metadata filter. + """ metadata: PropertyFilterNew + """ + Temporal property filter. + """ temporalProperty: PropertyFilterNew + """ + AND operator. + """ and: [EdgeFilter!] + """ + OR operator. + """ or: [EdgeFilter!] + """ + NOT operator. + """ not: EdgeFilter } @@ -367,20 +391,65 @@ input EdgeSortBy { } input EdgeViewCollection @oneOf { + """ + Contains only the default layer. + """ defaultLayer: Boolean + """ + Latest time. + """ latest: Boolean + """ + Snapshot at latest time. + """ snapshotLatest: Boolean + """ + Snapshot at specified time. + """ snapshotAt: Int + """ + List of included layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single included layer. + """ layer: String + """ + Single excluded layer. + """ excludeLayer: String + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int } @@ -512,20 +581,65 @@ type Edges { } input EdgesViewCollection @oneOf { + """ + Contains only the default layer. + """ defaultLayer: Boolean + """ + Latest time. + """ latest: Boolean + """ + Snapshot at latest time. + """ snapshotLatest: Boolean + """ + Snapshot at specified time. + """ snapshotAt: Int + """ + List of included layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single included layer. + """ layer: String + """ + Single excluded layer. + """ excludeLayer: String + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int } @@ -697,7 +811,7 @@ type Graph { """ Gets (optionally a subset of) the nodes in the graph. """ - nodes(ids: [String!]): Nodes! + nodes(ids: [String!], select: NodeFilter): Nodes! """ Gets the edge with the specified source and destination nodes. """ @@ -762,8 +876,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 { @@ -783,26 +897,89 @@ enum GraphType { } input GraphViewCollection @oneOf { + """ + Contains only the default layer. + """ defaultLayer: Boolean + """ + List of included layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single included layer. + """ layer: String + """ + Single excluded layer. + """ excludeLayer: String + """ + Subgraph nodes. + """ subgraph: [String!] + """ + Subgraph node types. + """ subgraphNodeTypes: [String!] + """ + List of excluded nodes. + """ excludeNodes: [String!] + """ + Valid state. + """ valid: Boolean + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + View at the latest time. + """ latest: Boolean + """ + Snapshot at specified time. + """ snapshotAt: Int + """ + Snapshot at latest time. + """ snapshotLatest: Boolean + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int + """ + Node filter. + """ nodeFilter: NodeFilter + """ + Edge filter. + """ edgeFilter: EdgeFilter } @@ -1312,8 +1489,17 @@ input NodeAddition { } enum NodeField { + """ + Node id. + """ NODE_ID + """ + Node name. + """ NODE_NAME + """ + Node type. + """ NODE_TYPE } @@ -1338,12 +1524,33 @@ input NodeFieldFilterNew { } input NodeFilter @oneOf { + """ + Node filter. + """ node: NodeFieldFilterNew + """ + Property filter. + """ property: PropertyFilterNew + """ + Metadata filter. + """ metadata: PropertyFilterNew + """ + Temporal property filter. + """ temporalProperty: PropertyFilterNew + """ + AND operator. + """ and: [NodeFilter!] + """ + OR operator. + """ or: [NodeFilter!] + """ + NOT operator. + """ not: NodeFilter } @@ -1376,21 +1583,69 @@ input NodeSortBy { } input NodeViewCollection @oneOf { + """ + Contains only the default layer. + """ defaultLayer: Boolean + """ + View at the latest time. + """ latest: Boolean + """ + Snapshot at latest time. + """ snapshotLatest: Boolean + """ + Snapshot at specified time. + """ snapshotAt: Int + """ + List of included layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single included layer. + """ layer: String + """ + Single excluded layer. + """ excludeLayer: String + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int + """ + Node filter. + """ nodeFilter: NodeFilter } @@ -1508,25 +1763,77 @@ type Nodes { Returns a view of the node ids. """ ids: [String!]! + select(filter: NodeFilter!): Nodes! } input NodesViewCollection @oneOf { + """ + Contains only the default layer. + """ defaultLayer: Boolean + """ + View at the latest time. + """ latest: Boolean + """ + Snapshot at latest time. + """ snapshotLatest: Boolean + """ + List of included layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single included layer. + """ layer: String + """ + Single excluded layer. + """ excludeLayer: String + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + Snapshot at specified time. + """ snapshotAt: Int + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int + """ + Node filter. + """ nodeFilter: NodeFilter + """ + List of types. + """ typeFilter: [String!] } @@ -1660,19 +1967,61 @@ type PathFromNode { } input PathFromNodeViewCollection @oneOf { + """ + Latest time. + """ latest: Boolean + """ + Latest snapshot. + """ snapshotLatest: Boolean + """ + Time. + """ snapshotAt: Int + """ + List of layers. + """ layers: [String!] + """ + List of excluded layers. + """ excludeLayers: [String!] + """ + Single layer. + """ layer: String + """ + Single layer to exclude. + """ excludeLayer: String + """ + Window between a start and end time. + """ window: Window + """ + View at a specified time. + """ at: Int + """ + View before a specified time (end exclusive). + """ before: Int + """ + View after a specified time (start exclusive). + """ after: Int + """ + Shrink a Window to a specified start and end time. + """ shrinkWindow: Window + """ + Set the window start to a specified time. + """ shrinkStart: Int + """ + Set the window end to a specified time. + """ shrinkEnd: Int } @@ -1704,7 +2053,6 @@ input PropCondition @oneOf { isNotIn: Value isSome: Boolean isNone: Boolean - between: [Value!] and: [PropCondition!] or: [PropCondition!] not: PropCondition diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 3b51e5c336..969592b8a9 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -330,18 +330,18 @@ impl PropCondition { Ge(_) => "ge", Lt(_) => "lt", Le(_) => "le", - + StartsWith(_) => "startsWith", EndsWith(_) => "endsWith", Contains(_) => "contains", NotContains(_) => "notContains", - + IsIn(_) => "isIn", IsNotIn(_) => "isNotIn", - + IsSome(_) => "isSome", IsNone(_) => "isNone", - + And(_) | Or(_) | Not(_) | First(_) | Last(_) | Any(_) | All(_) | Sum(_) | Avg(_) | Min(_) | Max(_) | Len(_) => return None, }) @@ -827,8 +827,8 @@ fn translate_prop_leaf_to_filter( IsSome(true) => (FO::IsSome, PropertyFilterValue::None), IsNone(true) => (FO::IsNone, PropertyFilterValue::None), - And(_) | Or(_) | Not(_) | First(_) | Last(_) | Any(_) | All(_) | Sum(_) - | Avg(_) | Min(_) | Max(_) | Len(_) | IsSome(false) | IsNone(false) => { + And(_) | Or(_) | Not(_) | First(_) | Last(_) | Any(_) | All(_) | Sum(_) | Avg(_) + | Min(_) | Max(_) | Len(_) | IsSome(false) | IsNone(false) => { return Err(GraphError::InvalidGqlFilter(format!( "Expected comparison at leaf for {}", name_for_errors @@ -1007,7 +1007,8 @@ impl TryFrom for CompositeEdgeFilter { "Src filter does not support NODE_TYPE".into(), )); } - let (_, field_value, operator) = translate_node_field_where(src.field, &src.where_)?; + let (_, field_value, operator) = + translate_node_field_where(src.field, &src.where_)?; Ok(CompositeEdgeFilter::Edge(Filter { field_name: "src".to_string(), field_value, @@ -1020,7 +1021,8 @@ impl TryFrom for CompositeEdgeFilter { "Dst filter does not support NODE_TYPE".into(), )); } - let (_, field_value, operator) = translate_node_field_where(dst.field, &dst.where_)?; + let (_, field_value, operator) = + translate_node_field_where(dst.field, &dst.where_)?; Ok(CompositeEdgeFilter::Edge(Filter { field_name: "dst".to_string(), field_value, diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index d10c7bcaf7..c055c2d06f 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, NodeViewOps, SearchableGraphOps, - StaticGraphViewOps, TimeOps, + BaseFilterOps, DynamicGraph, IntoDynamic, IterFilterOps, NodeViewOps, + SearchableGraphOps, StaticGraphViewOps, TimeOps, }, }, graph::{ @@ -374,12 +374,28 @@ impl GqlGraph { } /// Gets (optionally a subset of) the nodes in the graph. - async fn nodes(&self, ids: Option>) -> GqlNodes { - let nodes = self.graph.nodes(); - match ids { - None => GqlNodes::new(nodes), - Some(ids) => GqlNodes::new(blocking_compute(move || nodes.id_filter(ids)).await), + async fn nodes( + &self, + ids: Option>, + select: Option, + ) -> Result { + let base = self.graph.nodes(); + let nn = match ids { + None => base, + Some(ids) => blocking_compute(move || base.id_filter(ids)).await, + }; + + if let Some(sel) = select { + let nf: CompositeNodeFilter = sel.try_into()?; + let narrowed = blocking_compute({ + let nn_clone = nn.clone(); + move || nn_clone.filter_iter(nf) + }) + .await?; + return Ok(GqlNodes::new(narrowed.into_dyn())); } + + Ok(GqlNodes::new(nn)) } /// Gets the edge with the specified source and destination nodes. diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index 1b269f35f3..ea4e135c50 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -352,4 +352,14 @@ impl GqlNodes { let self_clone = self.clone(); blocking_compute(move || self_clone.nn.name().collect()).await } + + async fn select(&self, filter: NodeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let nf: CompositeNodeFilter = filter.try_into()?; + let narrowed = self_clone.nn.filter_iter(nf)?; + Ok(self_clone.update(narrowed.into_dyn())) + }) + .await + } } From bf8c67a31aea7bb326824837161b8ee7a87baff2 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:43:19 +0100 Subject: [PATCH 08/42] change semantics of filters in gql, add missing filter apis in edges, fix all tests --- .../test_filters/test_edge_filter_gql.py | 8 +- .../test_graph_edges_property_filter.py | 118 +++++----- .../test_graph_nodes_property_filter.py | 134 +++++------ .../test_filters/test_neighbours_filter.py | 30 +-- .../test_filters/test_node_filter_gql.py | 24 +- .../test_nodes_property_filter.py | 211 +++++++++++------- raphtory-graphql/schema.graphql | 32 ++- raphtory-graphql/src/model/graph/edge.rs | 29 ++- raphtory-graphql/src/model/graph/edges.rs | 29 ++- raphtory-graphql/src/model/graph/filtering.rs | 4 + raphtory-graphql/src/model/graph/graph.rs | 24 +- raphtory-graphql/src/model/graph/node.rs | 15 +- raphtory-graphql/src/model/graph/nodes.rs | 35 +-- raphtory/src/db/graph/edges.rs | 9 + 14 files changed, 411 insertions(+), 291 deletions(-) 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 e172d489cd..4a3a20586c 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 @@ -12,7 +12,7 @@ def test_filter_edges_with_str_ids_for_node_id_eq_gql(graph): query = """ query { graph(path: "g") { - edgeFilter(filter: { + filterEdges(expr: { src: { field: NODE_ID where: { eq: { str: "3" } } @@ -30,7 +30,7 @@ def test_filter_edges_with_str_ids_for_node_id_eq_gql(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"dst": {"name": "1"}, "src": {"name": "3"}}, @@ -52,7 +52,7 @@ def test_filter_edges_with_num_ids_for_node_id_eq_gql(graph): query = """ query { graph(path: "g") { - edgeFilter(filter: { + filterEdges(expr: { src: { field: NODE_ID where: { eq: { u64: 1 } } @@ -70,7 +70,7 @@ def test_filter_edges_with_num_ids_for_node_id_eq_gql(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "1"}, "dst": {"name": "2"}}]} } } 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 43519c960a..c4d58b443a 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 @@ -12,8 +12,8 @@ def test_graph_edge_property_filter_equal(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop5" where: { eq: { list: [{i64: 1},{i64: 2},{i64: 3}] } } @@ -27,7 +27,7 @@ def test_graph_edge_property_filter_equal(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "a"}, "dst": {"name": "d"}}]} } } @@ -40,8 +40,8 @@ def test_graph_edge_property_filter_equal_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop5" where: { eq: { i64: 1 } } @@ -64,8 +64,8 @@ def test_graph_edge_property_filter_not_equal(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop4" where: { ne: { bool: true } } @@ -79,7 +79,7 @@ def test_graph_edge_property_filter_not_equal(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "c"}, "dst": {"name": "d"}}]} } } @@ -92,8 +92,8 @@ def test_graph_edge_property_filter_not_equal_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop4" where: { ne: { i64: 1 } } @@ -116,8 +116,8 @@ def test_graph_edge_property_filter_greater_than_or_equal(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop1" where: { ge: { i64: 60 } } @@ -131,7 +131,7 @@ def test_graph_edge_property_filter_greater_than_or_equal(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "a"}, "dst": {"name": "d"}}]} } } @@ -144,8 +144,8 @@ def test_graph_edge_property_filter_greater_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop1" where: { ge: { bool: true } } @@ -168,8 +168,8 @@ def test_graph_edge_property_filter_less_than_or_equal(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { + filterEdges( + expr: { property: { name: "eprop1" where: { le: { i64: 30 } } @@ -183,7 +183,7 @@ def test_graph_edge_property_filter_less_than_or_equal(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "b"}, "dst": {"name": "d"}}, @@ -201,8 +201,8 @@ def test_graph_edge_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { le: { str: "shivam" } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { le: { str: "shivam" } } } } ) { edges { list { src { name } dst { name } } } } @@ -220,8 +220,8 @@ def test_graph_edge_property_filter_greater_than(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { gt: { i64: 30 } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { gt: { i64: 30 } } } } ) { edges { list { src { name } dst { name } } } } @@ -230,7 +230,7 @@ def test_graph_edge_property_filter_greater_than(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "a"}, "dst": {"name": "d"}}]} } } @@ -243,8 +243,8 @@ def test_graph_edge_property_filter_greater_than_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { gt: { str: "shivam" } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { gt: { str: "shivam" } } } } ) { edges { list { src { name } dst { name } } } } @@ -262,8 +262,8 @@ def test_graph_edge_property_filter_less_than(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { lt: { i64: 30 } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { lt: { i64: 30 } } } } ) { edges { list { src { name } dst { name } } } } @@ -272,7 +272,7 @@ def test_graph_edge_property_filter_less_than(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "b"}, "dst": {"name": "d"}}]} } } @@ -285,8 +285,8 @@ def test_graph_edge_property_filter_less_than_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { lt: { str: "shivam" } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { lt: { str: "shivam" } } } } ) { edges { list { src { name } dst { name } } } } @@ -304,15 +304,15 @@ def test_graph_edge_property_filter_is_none(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop5", where: { isNone: true } } } + filterEdges( + expr: { property: { name: "eprop5", where: { isNone: true } } } ) { edges { list { src { name } dst { name } } } } } } """ - expected_output = {"graph": {"edgeFilter": {"edges": {"list": []}}}} + expected_output = {"graph": {"filterEdges": {"edges": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -321,8 +321,8 @@ def test_graph_edge_property_filter_is_some(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop5", where: { isSome: true } } } + filterEdges( + expr: { property: { name: "eprop5", where: { isSome: true } } } ) { edges { list { src { name } dst { name } } } } @@ -331,7 +331,7 @@ def test_graph_edge_property_filter_is_some(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "a"}, "dst": {"name": "d"}}, @@ -350,8 +350,8 @@ def test_graph_edge_property_filter_is_in(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } ) { edges { list { src { name } dst { name } } } } @@ -360,7 +360,7 @@ def test_graph_edge_property_filter_is_in(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "b"}, "dst": {"name": "d"}}, @@ -378,15 +378,15 @@ def test_graph_edge_property_filter_is_empty_list(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isIn: { list: [] } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isIn: { list: [] } } } } ) { edges { list { src { name } dst { name } } } } } } """ - expected_output = {"graph": {"edgeFilter": {"edges": {"list": []}}}} + expected_output = {"graph": {"filterEdges": {"edges": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -395,8 +395,8 @@ def test_graph_edge_property_filter_is_in_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isIn: { str: "shivam" } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isIn: { str: "shivam" } } } } ) { edges { list { src { name } dst { name } } } } @@ -414,8 +414,8 @@ def test_graph_edge_property_filter_is_not_in(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isNotIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isNotIn: { list: [{i64: 10},{i64: 20},{i64: 30}] } } } } ) { edges { list { src { name } dst { name } } } } @@ -424,7 +424,7 @@ def test_graph_edge_property_filter_is_not_in(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": {"list": [{"src": {"name": "a"}, "dst": {"name": "d"}}]} } } @@ -437,8 +437,8 @@ def test_graph_edge_property_filter_is_not_in_empty_list(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isNotIn: { list: [] } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isNotIn: { list: [] } } } } ) { edges { list { src { name } dst { name } } } } @@ -447,7 +447,7 @@ def test_graph_edge_property_filter_is_not_in_empty_list(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "a"}, "dst": {"name": "d"}}, @@ -466,8 +466,8 @@ def test_graph_edge_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { - edgeFilter( - filter: { property: { name: "eprop1", where: { isNotIn: { str: "shivam" } } } } + filterEdges( + expr: { property: { name: "eprop1", where: { isNotIn: { str: "shivam" } } } } ) { edges { list { src { name } dst { name } } } } @@ -485,8 +485,8 @@ def test_graph_edge_not_property_filter(graph): query = """ query { graph(path: "g") { - edgeFilter ( - filter: { + filterEdges ( + expr: { not: { property: { name: "eprop5" @@ -502,7 +502,7 @@ def test_graph_edge_not_property_filter(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"dst": {"name": "d"}, "src": {"name": "a"}}, @@ -521,7 +521,7 @@ def test_edges_property_filter_starts_with(graph): query = """ query { graph(path: "g") { - edgeFilter(filter: { + filterEdges(expr: { property: { name: "eprop3" where: { startsWith: { str: "xyz" } } @@ -534,7 +534,7 @@ def test_edges_property_filter_starts_with(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "a"}, "dst": {"name": "d"}}, @@ -553,7 +553,7 @@ def test_edges_property_filter_ends_with(graph): query = """ query { graph(path: "g") { - edgeFilter(filter: { + filterEdges(expr: { property: { name: "eprop3" where: { endsWith: { str: "123" } } @@ -566,7 +566,7 @@ def test_edges_property_filter_ends_with(graph): """ expected_output = { "graph": { - "edgeFilter": { + "filterEdges": { "edges": { "list": [ {"src": {"name": "a"}, "dst": {"name": "d"}}, diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py index 95128896ea..a8f088b278 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_graph_nodes_property_filter.py @@ -12,8 +12,8 @@ def test_graph_node_property_filter_equal(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop5" where: { eq: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } } @@ -25,7 +25,7 @@ def test_graph_node_property_filter_equal(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -34,8 +34,8 @@ def test_graph_node_property_filter_equal_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop5" where: { eq: { i64: 1 } } @@ -58,8 +58,8 @@ def test_graph_node_property_filter_not_equal(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop4" where: { ne: { bool: true } } @@ -72,7 +72,7 @@ def test_graph_node_property_filter_not_equal(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -82,8 +82,8 @@ def test_graph_node_property_filter_not_equal_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop4" where: { ne: { i64: 1 } } @@ -106,8 +106,8 @@ def test_graph_node_property_filter_greater_than_or_equal(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop1" where: { ge: { i64: 60 } } @@ -119,7 +119,7 @@ def test_graph_node_property_filter_greater_than_or_equal(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -128,8 +128,8 @@ def test_graph_node_property_filter_greater_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop1" where: { ge: { bool: true } } @@ -152,8 +152,8 @@ def test_graph_node_property_filter_less_than_or_equal(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { property: { name: "prop1", where: { le: { i64: 30 } } } } ) { @@ -164,7 +164,7 @@ def test_graph_node_property_filter_less_than_or_equal(graph): """ expected_output = { "graph": { - "nodeFilter": { + "filterNodes": { "nodes": {"list": [{"name": "b"}, {"name": "c"}, {"name": "d"}]} } } @@ -177,8 +177,8 @@ def test_graph_node_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { le: { str: "shivam" } } } } + filterNodes( + expr: { property: { name: "prop1", where: { le: { str: "shivam" } } } } ) { nodes { list { name } } } @@ -196,15 +196,15 @@ def test_graph_node_property_filter_greater_than(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { gt: { i64: 30 } } } } + filterNodes( + expr: { property: { name: "prop1", where: { gt: { i64: 30 } } } } ) { nodes { list { name } } } } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -213,8 +213,8 @@ def test_graph_node_property_filter_greater_than_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { gt: { str: "shivam" } } } } + filterNodes( + expr: { property: { name: "prop1", where: { gt: { str: "shivam" } } } } ) { nodes { list { name } } } @@ -232,8 +232,8 @@ def test_graph_node_property_filter_less_than(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { lt: { i64: 30 } } } } + filterNodes( + expr: { property: { name: "prop1", where: { lt: { i64: 30 } } } } ) { nodes { list { name } } } @@ -241,7 +241,7 @@ def test_graph_node_property_filter_less_than(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "b"}, {"name": "c"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "b"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -251,8 +251,8 @@ def test_graph_node_property_filter_less_than_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { lt: { str: "shivam" } } } } + filterNodes( + expr: { property: { name: "prop1", where: { lt: { str: "shivam" } } } } ) { nodes { list { name } } } @@ -270,8 +270,8 @@ def test_graph_node_property_filter_is_none(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop5", where: { isNone: true } } } + filterNodes( + expr: { property: { name: "prop5", where: { isNone: true } } } ) { nodes { list { name } } } @@ -279,7 +279,7 @@ def test_graph_node_property_filter_is_none(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -289,8 +289,8 @@ def test_graph_node_property_filter_is_some(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop5", where: { isSome: true } } } + filterNodes( + expr: { property: { name: "prop5", where: { isSome: true } } } ) { nodes { list { name } } } @@ -298,7 +298,7 @@ def test_graph_node_property_filter_is_some(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -308,8 +308,8 @@ def test_graph_node_property_filter_is_in(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { isIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } + filterNodes( + expr: { property: { name: "prop1", where: { isIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } ) { nodes { list { name } } } @@ -317,7 +317,7 @@ def test_graph_node_property_filter_is_in(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -328,8 +328,8 @@ def test_node_property_filter_is_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { property: { name: "prop1", where: { isIn: { list: [] } } } } + select( + expr: { property: { name: "prop1", where: { isIn: { list: [] } } } } ) { list { name } } @@ -337,7 +337,7 @@ def test_node_property_filter_is_in_empty_list(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": []}}}} + expected_output = {"graph": {"nodes": {"select": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -347,15 +347,15 @@ def test_graph_node_property_filter_is_in_no_value(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { isIn: { list: [] } } } } + filterNodes( + expr: { property: { name: "prop1", where: { isIn: { list: [] } } } } ) { nodes { list { name } } } } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": []}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -364,8 +364,8 @@ def test_graph_node_property_filter_is_in_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { isIn: { str: "shivam" } } } } + filterNodes( + expr: { property: { name: "prop1", where: { isIn: { str: "shivam" } } } } ) { nodes { list { name } } } @@ -383,8 +383,8 @@ def test_graph_node_property_filter_is_not_in_any(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { isNotIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } + filterNodes( + expr: { property: { name: "prop1", where: { isNotIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } } } ) { nodes { list { name } } } @@ -392,7 +392,7 @@ def test_graph_node_property_filter_is_not_in_any(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -403,8 +403,8 @@ def test_node_property_filter_not_is_not_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { property: { name: "prop1", where: { isNotIn: { list: [] } } } } + filter( + expr: { property: { name: "prop1", where: { isNotIn: { list: [] } } } } ) { list { name } } @@ -415,7 +415,7 @@ def test_node_property_filter_not_is_not_in_empty_list(graph): expected_output = { "graph": { "nodes": { - "nodeFilter": { + "filter": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } } @@ -429,8 +429,8 @@ def test_graph_node_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { property: { name: "prop1", where: { isNotIn: { str: "shivam" } } } } + filterNodes( + expr: { property: { name: "prop1", where: { isNotIn: { str: "shivam" } } } } ) { nodes { list { name } } } @@ -448,8 +448,8 @@ def test_graph_node_not_property_filter(graph): query = """ query { graph(path: "g") { - nodeFilter ( - filter: { + filterNodes ( + expr: { not: { property: { name: "prop5" @@ -465,7 +465,7 @@ def test_graph_node_not_property_filter(graph): """ expected_output = { "graph": { - "nodeFilter": { + "filterNodes": { "nodes": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } @@ -481,7 +481,7 @@ def test_graph_node_type_and_property_filter(graph): query { graph(path: "g") { nodes { - nodeFilter(filter: { + select(expr: { and: [ { node: { @@ -507,7 +507,7 @@ def test_graph_node_type_and_property_filter(graph): expected_output = { "graph": { "nodes": { - "nodeFilter": { + "select": { "count": 3, "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}], } @@ -522,7 +522,7 @@ def test_graph_nodes_property_filter_starts_with(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { property: { name: "prop3" where: { startsWith: { str: "abc" } } @@ -535,7 +535,7 @@ def test_graph_nodes_property_filter_starts_with(graph): """ expected_output = { "graph": { - "nodeFilter": { + "filterNodes": { "nodes": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } @@ -550,7 +550,7 @@ def test_graph_nodes_property_filter_ends_with(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { property: { name: "prop3" where: { endsWith: { str: "123" } } @@ -561,7 +561,7 @@ def test_graph_nodes_property_filter_ends_with(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -570,8 +570,8 @@ def test_graph_nodes_property_filter_starts_with_temporal_any(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { temporalProperty: { name: "prop3", where: { any: { startsWith: { str: "abc1" } } } @@ -583,5 +583,5 @@ def test_graph_nodes_property_filter_starts_with_temporal_any(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py index 678a295d6e..f2aca0df8d 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py @@ -13,7 +13,7 @@ def test_out_neighbours_found(graph): query { graph(path: "g") { node(name: "a") { - nodeFilter(filter: { + filter(expr: { and: [ { node: { @@ -38,7 +38,7 @@ def test_out_neighbours_found(graph): } """ expected_output = { - "graph": {"node": {"nodeFilter": {"outNeighbours": {"list": [{"name": "d"}]}}}} + "graph": {"node": {"filter": {"outNeighbours": {"list": [{"name": "d"}]}}}} } run_graphql_test(query, expected_output, graph) @@ -49,7 +49,7 @@ def test_out_neighbours_not_found(graph): query { graph(path: "g") { node(name: "a") { - nodeFilter(filter: { + filter(expr: { node: { field: NODE_NAME, where: { eq: { str: "e" } } @@ -63,9 +63,7 @@ def test_out_neighbours_not_found(graph): } } """ - expected_output = { - "graph": {"node": {"nodeFilter": {"outNeighbours": {"list": []}}}} - } + expected_output = {"graph": {"node": {"filter": {"outNeighbours": {"list": []}}}}} run_graphql_test(query, expected_output, graph) @@ -75,7 +73,7 @@ def test_in_neighbours_found(graph): query { graph(path: "g") { node(name: "d") { - nodeFilter(filter: { + filter(expr: { property: { name: "prop1" where: { gt: { i64: 10 } } @@ -92,7 +90,7 @@ def test_in_neighbours_found(graph): expected_output = { "graph": { "node": { - "nodeFilter": {"inNeighbours": {"list": [{"name": "a"}, {"name": "c"}]}} + "filter": {"inNeighbours": {"list": [{"name": "a"}, {"name": "c"}]}} } } } @@ -105,7 +103,7 @@ def test_in_neighbours_not_found(graph): query { graph(path: "g") { node(name: "d") { - nodeFilter(filter: { + filter(expr: { node: { field: NODE_NAME, where: { eq: { str: "e" } } @@ -119,9 +117,7 @@ def test_in_neighbours_not_found(graph): } } """ - expected_output = { - "graph": {"node": {"nodeFilter": {"inNeighbours": {"list": []}}}} - } + expected_output = {"graph": {"node": {"filter": {"inNeighbours": {"list": []}}}}} run_graphql_test(query, expected_output, graph) @@ -131,7 +127,7 @@ def test_neighbours_found(graph): query { graph(path: "g") { node(name: "d") { - nodeFilter(filter: { + filter(expr: { node: { field: NODE_NAME, where: { ne: { str: "a" } } @@ -147,9 +143,7 @@ def test_neighbours_found(graph): """ expected_output = { "graph": { - "node": { - "nodeFilter": {"neighbours": {"list": [{"name": "b"}, {"name": "c"}]}} - } + "node": {"filter": {"neighbours": {"list": [{"name": "b"}, {"name": "c"}]}}} } } run_graphql_test(query, expected_output, graph) @@ -161,7 +155,7 @@ def test_neighbours_not_found(graph): query { graph(path: "g") { node(name: "d") { - nodeFilter(filter: { + filter(expr: { node: { field: NODE_NAME, where: { eq: { str: "e" } } @@ -175,5 +169,5 @@ def test_neighbours_not_found(graph): } } """ - expected_output = {"graph": {"node": {"nodeFilter": {"neighbours": {"list": []}}}}} + expected_output = {"graph": {"node": {"filter": {"neighbours": {"list": []}}}}} run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py index e361127af2..e6f597d938 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py @@ -12,8 +12,8 @@ def test_filter_nodes_with_str_ids_for_node_id_eq_gql(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { node: { field: NODE_ID where: { eq: { str: "1" } } @@ -27,7 +27,7 @@ def test_filter_nodes_with_str_ids_for_node_id_eq_gql(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "1"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "1"}]}}}} run_graphql_test(query, expected_output, graph) @@ -36,8 +36,8 @@ def test_filter_nodes_with_str_ids_for_node_id_eq_gql2(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { node: { field: NODE_ID where: { eq: { u64: 1 } } @@ -64,8 +64,8 @@ def test_filter_nodes_with_num_ids_for_node_id_eq_gql(graph): query = """ query { graph(path: "g") { - nodeFilter( - filter: { + filterNodes( + expr: { node: { field: NODE_ID where: { eq: { u64: 1 } } @@ -79,7 +79,7 @@ def test_filter_nodes_with_num_ids_for_node_id_eq_gql(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "1"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "1"}]}}}} run_graphql_test(query, expected_output, graph) @@ -89,12 +89,12 @@ def test_nodes_chained_selection_with_node_filter_by_node_and_prop_filter(graph) query { graph(path: "g") { nodes { - select(filter: { node: { + select(expr: { node: { field: NODE_TYPE where: { eq: { str: "fire_nation" } } } }) { - select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { - nodeFilter(filter:{ + select(expr: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + filter(expr:{ property: { name: "p100", where: { gt: { i64: 30 } } } }) { list { @@ -109,7 +109,7 @@ def test_nodes_chained_selection_with_node_filter_by_node_and_prop_filter(graph) """ expected_output = { "graph": { - "nodes": {"select": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + "nodes": {"select": {"select": {"filter": {"list": [{"name": "1"}]}}}} } } run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index 0e1605220d..e41cc3a844 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -14,13 +14,13 @@ @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_node_property_filter_equal(graph): +def test_node_property_filter_equal2(graph): query = """ query { graph(path: "g") { nodes { - nodeFilter( - filter: { + filter( + expr: { property: { name: "prop5" where: { @@ -30,14 +30,57 @@ def test_node_property_filter_equal(graph): } ) { list { - name + neighbours { + list { + name + } + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": { + "filter": { + "list": [ + {"neighbours": {"list": []}}, + {"neighbours": {"list": []}}, + {"neighbours": {"list": []}}, + {"neighbours": {"list": [{"name": "a"}]}}, + ] + } } + } + } + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_node_property_filter_equal3(graph): + query = """ + query { + graph(path: "g") { + nodes { + select( + expr: { + property: { + name: "prop5" + where: { + eq: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } + } + } + } + ) { + list { name } } } } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"nodes": {"select": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -47,8 +90,8 @@ def test_node_property_filter_equal_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop5" where: { @@ -77,8 +120,8 @@ def test_node_property_filter_not_equal(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop4" where: { @@ -96,7 +139,7 @@ def test_node_property_filter_not_equal(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -107,8 +150,8 @@ def test_node_property_filter_not_equal_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop4" where: { @@ -137,8 +180,8 @@ def test_node_property_filter_greater_than_or_equal(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { @@ -155,7 +198,7 @@ def test_node_property_filter_greater_than_or_equal(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"nodes": {"select": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -165,8 +208,8 @@ def test_node_property_filter_greater_than_or_equal_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { @@ -195,8 +238,8 @@ def test_node_property_filter_less_than_or_equal(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { @@ -215,9 +258,7 @@ def test_node_property_filter_less_than_or_equal(graph): """ expected_output = { "graph": { - "nodes": { - "nodeFilter": {"list": [{"name": "b"}, {"name": "c"}, {"name": "d"}]} - } + "nodes": {"select": {"list": [{"name": "b"}, {"name": "c"}, {"name": "d"}]}} } } run_graphql_test(query, expected_output, graph) @@ -229,8 +270,8 @@ def test_node_property_filter_less_than_or_equal_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { le: { str: "shivam" } } @@ -255,8 +296,8 @@ def test_node_property_filter_greater_than(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { gt: { i64: 30 } } @@ -269,7 +310,7 @@ def test_node_property_filter_greater_than(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"nodes": {"select": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -279,8 +320,8 @@ def test_node_property_filter_greater_than_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { gt: { str: "shivam" } } @@ -305,8 +346,8 @@ def test_node_property_filter_less_than(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { lt: { i64: 30 } } @@ -320,7 +361,7 @@ def test_node_property_filter_less_than(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "b"}, {"name": "c"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "b"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -331,8 +372,8 @@ def test_node_property_filter_less_than_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { lt: { str: "shivam" } } @@ -357,8 +398,8 @@ def test_node_property_filter_is_none(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop5" where: { isNone: true } @@ -372,7 +413,7 @@ def test_node_property_filter_is_none(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -383,8 +424,8 @@ def test_node_property_filter_is_some(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop5" where: { isSome: true } @@ -398,7 +439,7 @@ def test_node_property_filter_is_some(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}, {"name": "c"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -409,8 +450,8 @@ def test_node_property_filter_is_in(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } @@ -424,7 +465,7 @@ def test_node_property_filter_is_in(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "b"}, {"name": "d"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "b"}, {"name": "d"}]}}} } run_graphql_test(query, expected_output, graph) @@ -435,8 +476,8 @@ def test_node_property_filter_is_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isIn: { list: [] } } @@ -449,7 +490,7 @@ def test_node_property_filter_is_in_empty_list(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": []}}}} + expected_output = {"graph": {"nodes": {"select": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -460,8 +501,8 @@ def test_node_property_filter_is_in_no_value(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isIn: { list: [{i64: 100}] } } @@ -474,7 +515,7 @@ def test_node_property_filter_is_in_no_value(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": []}}}} + expected_output = {"graph": {"nodes": {"select": {"list": []}}}} run_graphql_test(query, expected_output, graph) @@ -484,8 +525,8 @@ def test_node_property_filter_is_in_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isIn: { str: "shivam" } } @@ -510,8 +551,8 @@ def test_node_property_filter_is_not_in(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isNotIn: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}] } } @@ -525,7 +566,7 @@ def test_node_property_filter_is_not_in(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}, {"name": "c"}]}}} + "graph": {"nodes": {"select": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -536,8 +577,8 @@ def test_node_property_filter_is_not_in_empty_list(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isNotIn: { list: [] } } @@ -553,7 +594,7 @@ def test_node_property_filter_is_not_in_empty_list(graph): expected_output = { "graph": { "nodes": { - "nodeFilter": { + "select": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } } @@ -568,8 +609,8 @@ def test_node_property_filter_is_not_in_type_error(graph): query { graph(path: "g") { nodes { - nodeFilter( - filter: { + select( + expr: { property: { name: "prop1" where: { isNotIn: { str: "shivam" } } @@ -593,7 +634,7 @@ def test_node_property_filter_contains_wrong_value_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { property: { name: "p10" where: { contains: { u64: 2 } } @@ -616,7 +657,7 @@ def test_nodes_property_filter_starts_with(graph): query { graph(path: "g") { nodes { - nodeFilter(filter: { + select(expr: { property: { name: "prop3" where: { startsWith: { str: "abc" } } @@ -631,7 +672,7 @@ def test_nodes_property_filter_starts_with(graph): expected_output = { "graph": { "nodes": { - "nodeFilter": { + "select": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } } @@ -646,7 +687,7 @@ def test_nodes_property_filter_ends_with(graph): query { graph(path: "g") { nodes { - nodeFilter(filter: { + select(expr: { property: { name: "prop3" where: { endsWith: { str: "333" } } @@ -658,7 +699,7 @@ def test_nodes_property_filter_ends_with(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "c"}]}}}} + expected_output = {"graph": {"nodes": {"select": {"list": [{"name": "c"}]}}}} run_graphql_test(query, expected_output, graph) @@ -668,7 +709,7 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): query { graph(path: "g") { nodes { - nodeFilter(filter: { + select(expr: { temporalProperty: { name: "prop3" where: { first: { startsWith: { str: "abc" } } } @@ -683,7 +724,7 @@ def test_nodes_property_filter_temporal_first_starts_with(graph): expected_output = { "graph": { "nodes": { - "nodeFilter": { + "select": { "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] } } @@ -698,7 +739,7 @@ def test_nodes_property_filter_temporal_all_starts_with(graph): query { graph(path: "g") { nodes { - nodeFilter(filter: { + select(expr: { temporalProperty: { name: "prop3" where: { any: { startsWith: { str: "abc1" } } } @@ -710,7 +751,7 @@ def test_nodes_property_filter_temporal_all_starts_with(graph): } } """ - expected_output = {"graph": {"nodes": {"nodeFilter": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"nodes": {"select": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -720,7 +761,7 @@ def test_nodes_property_filter_list_agg(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { property: { name: "prop5" where: { sum: { eq: { i64: 6 } } } @@ -731,7 +772,7 @@ def test_nodes_property_filter_list_agg(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}]}}}} run_graphql_test(query, expected_output, graph) @@ -740,7 +781,7 @@ def test_nodes_property_filter_list_qualifier(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { property: { name: "prop5" where: { any: { eq: { i64: 6 } } } @@ -751,7 +792,7 @@ def test_nodes_property_filter_list_qualifier(graph): } } """ - expected_output = {"graph": {"nodeFilter": {"nodes": {"list": [{"name": "c"}]}}}} + expected_output = {"graph": {"filterNodes": {"nodes": {"list": [{"name": "c"}]}}}} run_graphql_test(query, expected_output, graph) @@ -764,7 +805,7 @@ def test_nodes_temporal_property_filter_agg(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { temporalProperty: { name: "p2" where: { avg: { lt: { f64: 10.0 } } } @@ -776,7 +817,7 @@ def test_nodes_temporal_property_filter_agg(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "2"}, {"name": "3"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "2"}, {"name": "3"}]}}} } run_graphql_test(query, expected_output, graph) @@ -791,7 +832,7 @@ def test_nodes_temporal_property_filter_any_avg(graph): query = """ query { graph(path: "g") { - nodeFilter(filter: { + filterNodes(expr: { temporalProperty: { name: "prop5" where: { any: { avg: { lt: { f64: 10.0 } } } } @@ -803,7 +844,7 @@ def test_nodes_temporal_property_filter_any_avg(graph): } """ expected_output = { - "graph": {"nodeFilter": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} + "graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} } run_graphql_test(query, expected_output, graph) @@ -852,7 +893,7 @@ def test_nodes_selection_with_node_filter_by_prop_filter(graph): query { graph(path: "g") { nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { - nodeFilter(filter:{ + filter(expr:{ property: { name: "p100", where: { gt: { i64: 30 } } } }) { list { @@ -864,7 +905,7 @@ def test_nodes_selection_with_node_filter_by_prop_filter(graph): } """ expected_output = { - "graph": {"nodes": {"nodeFilter": {"list": [{"name": "1"}, {"name": "3"}]}}} + "graph": {"nodes": {"filter": {"list": [{"name": "1"}, {"name": "3"}]}}} } run_graphql_test(query, expected_output, graph) @@ -875,8 +916,8 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter(graph): query { graph(path: "g") { nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { - select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { - nodeFilter(filter:{ + select(expr: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + filter(expr:{ property: { name: "p100", where: { gt: { i64: 30 } } } }) { list { @@ -889,7 +930,7 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter(graph): } """ expected_output = { - "graph": {"nodes": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + "graph": {"nodes": {"select": {"filter": {"list": [{"name": "1"}]}}}} } run_graphql_test(query, expected_output, graph) @@ -900,9 +941,9 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter_ver2(graph): query { graph(path: "g") { nodes { - select(filter: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { - select(filter: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { - nodeFilter(filter:{ + select(expr: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + select(expr: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { + filter(expr:{ property: { name: "p100", where: { gt: { i64: 30 } } } }) { list { @@ -917,7 +958,7 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter_ver2(graph): """ expected_output = { "graph": { - "nodes": {"select": {"select": {"nodeFilter": {"list": [{"name": "1"}]}}}} + "nodes": {"select": {"select": {"filter": {"list": [{"name": "1"}]}}}} } } run_graphql_test(query, expected_output, graph) diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index 5160973635..a653dc3a1e 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -291,6 +291,7 @@ type Edge { Returns: boolean """ isSelfLoop: Boolean! + filter(expr: EdgeFilter!): Edge! } input EdgeAddition { @@ -451,6 +452,10 @@ input EdgeViewCollection @oneOf { Set the window end to a specified time. """ shrinkEnd: Int + """ + Edge filter + """ + edgeFilter: EdgeFilter } type EdgeWindowSet { @@ -578,6 +583,8 @@ type Edges { Returns a list of all objects in the current selection of the collection. You should filter filter the collection first then call list. """ list: [Edge!]! + filter(expr: EdgeFilter!): Edges! + select(expr: EdgeFilter!): Edges! } input EdgesViewCollection @oneOf { @@ -641,6 +648,10 @@ input EdgesViewCollection @oneOf { Set the window end to a specified time. """ shrinkEnd: Int + """ + Edge filter + """ + edgeFilter: EdgeFilter } type EdgesWindowSet { @@ -819,7 +830,7 @@ type Graph { """ Gets the edges in the graph. """ - edges: Edges! + edges(select: EdgeFilter): Edges! """ Returns the properties of the graph. """ @@ -850,8 +861,8 @@ type Graph { Export all nodes and edges from this graph view to another existing graph """ exportTo(path: String!): Boolean! - nodeFilter(filter: NodeFilter!): Graph! - edgeFilter(filter: EdgeFilter!): Graph! + filterNodes(expr: NodeFilter!): Graph! + filterEdges(expr: EdgeFilter!): Graph! """ (Experimental) Get index specification. """ @@ -1466,7 +1477,7 @@ type Node { Returns the number of neighbours that have at least one out-going edge from this node. """ outNeighbours: PathFromNode! - nodeFilter(filter: NodeFilter!): Node! + filter(expr: NodeFilter!): Node! } input NodeAddition { @@ -1735,10 +1746,6 @@ type Nodes { Filter nodes by node type. """ typeFilter(nodeTypes: [String!]!): Nodes! - """ - Returns a view of the node types. - """ - nodeFilter(filter: NodeFilter!): Nodes! applyViews(views: [NodesViewCollection!]!): Nodes! sorted(sortBys: [NodeSortBy!]!): Nodes! """ @@ -1763,7 +1770,14 @@ type Nodes { Returns a view of the node ids. """ ids: [String!]! - select(filter: NodeFilter!): Nodes! + """ + Returns a view of the node types. + """ + filter(expr: NodeFilter!): Nodes! + """ + Returns filtered list of nodes + """ + select(expr: NodeFilter!): Nodes! } input NodesViewCollection @oneOf { diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index 5e592b70f6..e5f3ee641b 100644 --- a/raphtory-graphql/src/model/graph/edge.rs +++ b/raphtory-graphql/src/model/graph/edge.rs @@ -1,7 +1,7 @@ use crate::{ model::graph::{ edges::GqlEdges, - filtering::EdgeViewCollection, + filtering::{EdgeFilter, EdgeViewCollection, NodeFilter}, node::GqlNode, property::{GqlMetadata, GqlProperties}, windowset::GqlEdgeWindowSet, @@ -13,8 +13,14 @@ use crate::{ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ db::{ - api::view::{DynamicGraph, EdgeViewOps, IntoDynamic, StaticGraphViewOps}, - graph::edge::EdgeView, + api::view::{BaseFilterOps, DynamicGraph, EdgeViewOps, IntoDynamic, StaticGraphViewOps}, + graph::{ + edge::EdgeView, + node::NodeView, + views::filter::model::{ + edge_filter::CompositeEdgeFilter, node_filter::CompositeNodeFilter, + }, + }, }, errors::GraphError, prelude::{LayerOps, TimeOps}, @@ -232,6 +238,7 @@ impl GqlEdge { } EdgeViewCollection::ShrinkStart(time) => return_view.shrink_start(time).await, EdgeViewCollection::ShrinkEnd(time) => return_view.shrink_end(time).await, + EdgeViewCollection::EdgeFilter(filter) => return_view.filter(filter).await?, } } Ok(return_view) @@ -368,4 +375,20 @@ impl GqlEdge { async fn is_self_loop(&self) -> bool { self.ee.is_self_loop() } + + async fn filter(&self, expr: EdgeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeEdgeFilter = expr.try_into()?; + let filtered = self_clone.ee.filter(filter)?; + Ok(self_clone.update(filtered.into_dynamic())) + }) + .await + } +} + +impl GqlEdge { + fn update>>(&self, edge: E) -> Self { + Self { ee: edge.into() } + } } diff --git a/raphtory-graphql/src/model/graph/edges.rs b/raphtory-graphql/src/model/graph/edges.rs index 41c19811e6..5ff1baa3a6 100644 --- a/raphtory-graphql/src/model/graph/edges.rs +++ b/raphtory-graphql/src/model/graph/edges.rs @@ -2,7 +2,7 @@ use crate::{ model::{ graph::{ edge::GqlEdge, - filtering::EdgesViewCollection, + filtering::{EdgeFilter, EdgesViewCollection}, windowset::GqlEdgesWindowSet, WindowDuration, WindowDuration::{Duration, Epoch}, @@ -15,8 +15,8 @@ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use itertools::Itertools; use raphtory::{ db::{ - api::view::{internal::BaseFilter, DynamicGraph}, - graph::edges::Edges, + api::view::{internal::BaseFilter, DynamicGraph, IterFilterOps}, + graph::{edges::Edges, views::filter::model::edge_filter::CompositeEdgeFilter}, }, errors::GraphError, prelude::*, @@ -24,6 +24,8 @@ use raphtory::{ use raphtory_api::iter::IntoDynBoxed; use std::{cmp::Ordering, sync::Arc}; +use raphtory::db::api::view::BaseFilterOps; + #[derive(ResolvedObject, Clone)] #[graphql(name = "Edges")] pub(crate) struct GqlEdges { @@ -220,6 +222,7 @@ impl GqlEdges { } EdgesViewCollection::ShrinkStart(time) => return_view.shrink_start(time).await, EdgesViewCollection::ShrinkEnd(time) => return_view.shrink_end(time).await, + EdgesViewCollection::EdgeFilter(filter) => return_view.select(filter).await?, } } @@ -348,4 +351,24 @@ impl GqlEdges { let self_clone = self.clone(); blocking_compute(move || self_clone.iter().collect()).await } + + async fn filter(&self, expr: EdgeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeEdgeFilter = expr.try_into()?; + let filtered = self_clone.ee.filter(filter)?; + Ok(self_clone.update(filtered.into_dyn())) + }) + .await + } + + async fn select(&self, expr: EdgeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeEdgeFilter = expr.try_into()?; + let filtered = self_clone.ee.filter_iter(filter)?; + Ok(self_clone.update(filtered)) + }) + .await + } } diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 969592b8a9..fcb5d1d08a 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -186,6 +186,8 @@ pub enum EdgesViewCollection { ShrinkStart(i64), /// Set the window end to a specified time. ShrinkEnd(i64), + /// Edge filter + EdgeFilter(EdgeFilter), } #[derive(OneOfInput, Clone, Debug)] @@ -220,6 +222,8 @@ pub enum EdgeViewCollection { ShrinkStart(i64), /// Set the window end to a specified time. ShrinkEnd(i64), + /// Edge filter + EdgeFilter(EdgeFilter), } #[derive(OneOfInput, Clone, Debug)] diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index c055c2d06f..7873bc57e2 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -404,8 +404,16 @@ impl GqlGraph { } /// Gets the edges in the graph. - async fn edges<'a>(&self) -> GqlEdges { - GqlEdges::new(self.graph.edges()) + async fn edges<'a>(&self, select: Option) -> Result { + let base = self.graph.edges(); + + if let Some(sel) = select { + let ef: CompositeEdgeFilter = sel.try_into()?; + let narrowed = blocking_compute(move || base.filter_iter(ef)).await?; + return Ok(GqlEdges::new(narrowed)); + } + + Ok(GqlEdges::new(base)) } //////////////////////// @@ -518,10 +526,10 @@ impl GqlGraph { .await } - async fn node_filter(&self, filter: NodeFilter) -> Result { + async fn filter_nodes(&self, expr: NodeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { - let filter: CompositeNodeFilter = filter.try_into()?; + let filter: CompositeNodeFilter = expr.try_into()?; let filtered_graph = self_clone.graph.filter(filter)?; Ok(GqlGraph::new( self_clone.path.clone(), @@ -531,10 +539,10 @@ impl GqlGraph { .await } - async fn edge_filter(&self, filter: EdgeFilter) -> Result { + async fn filter_edges(&self, expr: EdgeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { - let filter: CompositeEdgeFilter = filter.try_into()?; + let filter: CompositeEdgeFilter = expr.try_into()?; let filtered_graph = self_clone.graph.filter(filter)?; Ok(GqlGraph::new( self_clone.path.clone(), @@ -677,8 +685,8 @@ impl GqlGraph { } GraphViewCollection::ShrinkStart(start) => return_view.shrink_start(start).await, GraphViewCollection::ShrinkEnd(end) => return_view.shrink_end(end).await, - GraphViewCollection::NodeFilter(filter) => return_view.node_filter(filter).await?, - GraphViewCollection::EdgeFilter(filter) => return_view.edge_filter(filter).await?, + GraphViewCollection::NodeFilter(filter) => return_view.filter_nodes(filter).await?, + GraphViewCollection::EdgeFilter(filter) => return_view.filter_edges(filter).await?, }; } Ok(return_view) diff --git a/raphtory-graphql/src/model/graph/node.rs b/raphtory-graphql/src/model/graph/node.rs index 29befda8ca..2f4b801fe3 100644 --- a/raphtory-graphql/src/model/graph/node.rs +++ b/raphtory-graphql/src/model/graph/node.rs @@ -15,7 +15,10 @@ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ algorithms::components::{in_component, out_component}, db::{ - api::{properties::dyn_props::DynProperties, view::*}, + api::{ + properties::dyn_props::DynProperties, + view::{BaseFilterOps, *}, + }, graph::{node::NodeView, views::filter::model::node_filter::CompositeNodeFilter}, }, errors::GraphError, @@ -220,7 +223,7 @@ impl GqlNode { } NodeViewCollection::ShrinkStart(time) => return_view.shrink_start(time).await, NodeViewCollection::ShrinkEnd(time) => return_view.shrink_end(time).await, - NodeViewCollection::NodeFilter(filter) => return_view.node_filter(filter).await?, + NodeViewCollection::NodeFilter(filter) => return_view.filter(filter).await?, } } Ok(return_view) @@ -365,12 +368,12 @@ impl GqlNode { GqlPathFromNode::new(self.vv.out_neighbours()) } - async fn node_filter(&self, filter: NodeFilter) -> Result { + async fn filter(&self, expr: NodeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { - let filter: CompositeNodeFilter = filter.try_into()?; - let filtered_nodes_applied = self_clone.vv.filter(filter)?; - Ok(self_clone.update(filtered_nodes_applied.into_dynamic())) + let filter: CompositeNodeFilter = expr.try_into()?; + let filtered = self_clone.vv.filter(filter)?; + Ok(self_clone.update(filtered.into_dynamic())) }) .await } diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index ea4e135c50..811f7329d3 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -17,7 +17,7 @@ use raphtory::{ db::{ api::{ state::Index, - view::{DynamicGraph, IterFilterOps}, + view::{BaseFilterOps, DynamicGraph, IterFilterOps}, }, graph::{nodes::Nodes, views::filter::model::node_filter::CompositeNodeFilter}, }, @@ -180,17 +180,6 @@ impl GqlNodes { blocking_compute(move || self_clone.update(self_clone.nn.type_filter(&node_types))).await } - /// Returns a view of the node types. - async fn node_filter(&self, filter: NodeFilter) -> Result { - let self_clone = self.clone(); - blocking_compute(move || { - let filter: CompositeNodeFilter = filter.try_into()?; - let filtered_nodes = self_clone.nn.filter_iter(filter)?; - Ok(self_clone.update(filtered_nodes.into_dyn())) - }) - .await - } - async fn apply_views(&self, views: Vec) -> Result { let mut return_view: GqlNodes = GqlNodes::new(self.nn.clone()); for view in views { @@ -235,7 +224,7 @@ impl GqlNodes { NodesViewCollection::ShrinkStart(time) => return_view.shrink_start(time).await, NodesViewCollection::ShrinkEnd(time) => return_view.shrink_end(time).await, NodesViewCollection::NodeFilter(node_filter) => { - return_view.node_filter(node_filter).await? + return_view.select(node_filter).await? } NodesViewCollection::TypeFilter(types) => return_view.type_filter(types).await, } @@ -353,12 +342,24 @@ impl GqlNodes { blocking_compute(move || self_clone.nn.name().collect()).await } - async fn select(&self, filter: NodeFilter) -> Result { + /// Returns a view of the node types. + async fn filter(&self, expr: NodeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeNodeFilter = expr.try_into()?; + let filtered = self_clone.nn.filter(filter)?; + Ok(self_clone.update(filtered.into_dyn())) + }) + .await + } + + /// Returns filtered list of nodes + async fn select(&self, expr: NodeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { - let nf: CompositeNodeFilter = filter.try_into()?; - let narrowed = self_clone.nn.filter_iter(nf)?; - Ok(self_clone.update(narrowed.into_dyn())) + let filter: CompositeNodeFilter = expr.try_into()?; + let filtered = self_clone.nn.filter_iter(filter)?; + Ok(self_clone.update(filtered.into_dyn())) }) .await } diff --git a/raphtory/src/db/graph/edges.rs b/raphtory/src/db/graph/edges.rs index 7d0c7cea43..72ee168453 100644 --- a/raphtory/src/db/graph/edges.rs +++ b/raphtory/src/db/graph/edges.rs @@ -29,6 +29,15 @@ pub struct Edges<'graph, G> { pub(crate) edges: Arc BoxedLIter<'graph, EdgeRef> + Send + Sync + 'graph>, } +impl<'graph, G: IntoDynamic> Edges<'graph, G> { + pub fn into_dyn(self) -> Edges<'graph, DynamicGraph> { + Edges { + base_graph: self.base_graph.into_dynamic(), + edges: self.edges, + } + } +} + impl<'graph, G: GraphViewOps<'graph>> Debug for Edges<'graph, G> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() From 3c17197fca09b4543e8d313f6e622cc98460edb6 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:30:19 +0100 Subject: [PATCH 09/42] add more edge filter tests --- .../test_filters/test_edge_filter_gql.py | 24 ++++ .../test_graph_edges_property_filter.py | 104 +++++++++++++++++- .../test_filters/test_node_filter_gql.py | 2 +- .../test_nodes_property_filter.py | 69 ++++++++++-- raphtory-graphql/schema.graphql | 2 +- 5 files changed, 191 insertions(+), 10 deletions(-) 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 4a3a20586c..9796f8049c 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 @@ -76,3 +76,27 @@ def test_filter_edges_with_num_ids_for_node_id_eq_gql(graph): } } run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_edges_chained_selection_with_edge_filter(graph): + query = """ + query { + graph(path: "g") { + edges { + select(expr: { dst: { + field: NODE_ID + where: { eq: { u64: 2 } } + } }) { + select(expr: { property: { name: "p2", where: { gt:{ i64: 2 } } } }) { + list { src { name } dst { name } } + } + } + } + } + } + """ + expected_output = {'graph': {'edges': {'select': {'select': {'list': [ + {'dst': {'name': '2'}, 'src': {'name': '1'}} + ]}}}}} + run_graphql_test(query, expected_output, graph) 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 c4d58b443a..dc56b658a0 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 @@ -1,6 +1,6 @@ import pytest from raphtory import Graph, PersistentGraph -from filters_setup import create_test_graph +from filters_setup import create_test_graph, init_graph2 from utils import run_graphql_test, run_graphql_error_test EVENT_GRAPH = create_test_graph(Graph()) @@ -578,3 +578,105 @@ def test_edges_property_filter_ends_with(graph): } } run_graphql_test(query, expected_output, graph) + + +EVENT_GRAPH = init_graph2(Graph()) +PERSISTENT_GRAPH = init_graph2(PersistentGraph()) + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_edges_selection(graph): + query = """ + query { + graph(path: "g") { + edges(select: { property: { name: "p2", where: { gt: { i64: 3 } } } }) { + list { src { name } dst { name } } + } + } + } + """ + expected_output = {'graph': {'edges': {'list': [ + {'dst': {'name': '2'}, 'src': {'name': '1'}}, + {'dst': {'name': '1'}, 'src': {'name': '3'}}, + {'dst': {'name': '4'}, 'src': {'name': '3'}}, + {'dst': {'name': '1'}, 'src': {'name': '2'}}] + }}} + run_graphql_test(query, expected_output, graph) + + +# The inner edges filter has no effect on the list of edges returned from selection filter +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_edges_selection_edges_filter_paired(graph): + query = """ + query { + graph(path: "g") { + edges(select: { property: { name: "p2", where: { gt: { i64: 3 } } } }) { + filter(expr:{ + property: { name: "p3", where: { eq:{ i64: 5 } } } + }) { + list { src { name } dst { name } } + } + } + } + } + """ + expected_output = {'graph': {'edges': {'filter': {'list': [ + {'dst': {'name': '2'}, 'src': {'name': '1'}}, + {'dst': {'name': '1'}, 'src': {'name': '3'}}, + {'dst': {'name': '4'}, 'src': {'name': '3'}}, + {'dst': {'name': '1'}, 'src': {'name': '2'}}] + }}}} + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_edges_chained_selection_edges_filter_paired(graph): + query = """ + query { + graph(path: "g") { + edges(select: { property: { name: "p2", where: { gt: { i64: 3 } } } }) { + select(expr: { property: { name: "p2", where: { lt: { i64: 5 } } } }) { + filter(expr: { + dst: { + field: NODE_ID + where: { eq: { u64: 2 } } + } + }) { + list { src { name } dst { name } } + } + } + } + } + } + """ + expected_output = {'graph': {'edges': {'select': {'filter': {'list': [ + {'dst': {'name': '2'}, 'src': {'name': '1'}} + ]}}}}} + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_edges_chained_selection_edges_filter_paired_ver2(graph): + query = """ + query { + graph(path: "g") { + edges { + select(expr: { property: { name: "p2", where: { gt: { i64: 3 } } } }) { + select(expr: { property: { name: "p2", where: { lt: { i64: 5 } } } }) { + filter(expr: { + dst: { + field: NODE_ID + where: { eq: { u64: 2 } } + } + }) { + list { src { name } dst { name } } + } + } + } + } + } + } + """ + expected_output = {'graph': {'edges': {'select': {'select': {'filter': {'list': [ + {'dst': {'name': '2'}, 'src': {'name': '1'}} + ]}}}}}} + run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py index e6f597d938..6eed06369d 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_node_filter_gql.py @@ -84,7 +84,7 @@ def test_filter_nodes_with_num_ids_for_node_id_eq_gql(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_chained_selection_with_node_filter_by_node_and_prop_filter(graph): +def test_nodes_chained_selection_with_node_filter(graph): query = """ query { graph(path: "g") { diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index e41cc3a844..7cfebb8d76 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -854,7 +854,7 @@ def test_nodes_temporal_property_filter_any_avg(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_selection_by_prop_filter(graph): +def test_nodes_neighbours_selection_with_prop_filter(graph): query = """ query { graph(path: "g") { @@ -888,13 +888,33 @@ def test_nodes_selection_by_prop_filter(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_selection_with_node_filter_by_prop_filter(graph): +def test_nodes_selection(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + list { + name + } + } + } + } + """ + expected_output = { + "graph": {"nodes": {"list": [{"name": "1"}, {"name": "3"}]}} + } + run_graphql_test(query, expected_output, graph) + + +# The inner nodes filter has no effect on the list of nodes returned from selection filter +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_selection_nodes_filter_paired(graph): query = """ query { graph(path: "g") { nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { filter(expr:{ - property: { name: "p100", where: { gt: { i64: 30 } } } + property: { name: "p9", where: { eq:{ i64: 5 } } } }) { list { name @@ -910,15 +930,47 @@ def test_nodes_selection_with_node_filter_by_prop_filter(graph): run_graphql_test(query, expected_output, graph) +# The inner nodes filter has effect on the neighbours list +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_selection_nodes_filter_paired2(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + filter(expr:{ + property: { name: "p9", where: { eq:{ i64: 5 } } } + }) { + list { + neighbours { + list { + name + } + } + } + } + } + } + } + """ + expected_output = {'graph': {'nodes': {'filter': {'list': [ + {'neighbours': {'list': []}}, + {'neighbours': {'list': [{'name': '1'}]}} + ]}}}} + run_graphql_test(query, expected_output, graph) + + @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_chained_selection_with_node_filter_by_prop_filter(graph): +def test_nodes_chained_selection_node_filter_paired(graph): query = """ query { graph(path: "g") { nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { select(expr: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { filter(expr:{ - property: { name: "p100", where: { gt: { i64: 30 } } } + node: { + field: NODE_TYPE + where: { eq: { str: "fire_nation" } } + } }) { list { name @@ -936,7 +988,7 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter(graph): @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) -def test_nodes_chained_selection_with_node_filter_by_prop_filter_ver2(graph): +def test_nodes_chained_selection_node_filter_paired_ver2(graph): query = """ query { graph(path: "g") { @@ -944,7 +996,10 @@ def test_nodes_chained_selection_with_node_filter_by_prop_filter_ver2(graph): select(expr: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { select(expr: { property: { name: "p9", where: { eq:{ i64: 5 } } } }) { filter(expr:{ - property: { name: "p100", where: { gt: { i64: 30 } } } + node: { + field: NODE_TYPE + where: { eq: { str: "fire_nation" } } + } }) { list { name diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index a653dc3a1e..f9404616fe 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -887,8 +887,8 @@ type Graph { } type GraphAlgorithmPlugin { - pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! shortest_path(source: String!, targets: [String!]!, direction: String): [ShortestPathOutput!]! + pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! } type GraphSchema { From 10224c4ed2d90ab05eeac577f0ed8aa6507f250d Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:32:56 +0100 Subject: [PATCH 10/42] add filtering to path from node, add tests --- .../test_filters/test_edge_filter_gql.py | 12 ++- .../test_graph_edges_property_filter.py | 67 +++++++++---- .../test_filters/test_neighbours_filter.py | 97 ++++++++++++++++++- .../test_nodes_property_filter.py | 20 ++-- raphtory-graphql/schema.graphql | 12 ++- raphtory-graphql/src/model/graph/edges.rs | 2 + raphtory-graphql/src/model/graph/nodes.rs | 2 +- .../src/model/graph/path_from_node.rs | 29 +++++- raphtory/src/db/graph/path.rs | 9 ++ .../views/filter/model/property_filter.rs | 3 +- 10 files changed, 217 insertions(+), 36 deletions(-) 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 9796f8049c..b15f64ac2a 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 @@ -96,7 +96,13 @@ def test_edges_chained_selection_with_edge_filter(graph): } } """ - expected_output = {'graph': {'edges': {'select': {'select': {'list': [ - {'dst': {'name': '2'}, 'src': {'name': '1'}} - ]}}}}} + expected_output = { + "graph": { + "edges": { + "select": { + "select": {"list": [{"dst": {"name": "2"}, "src": {"name": "1"}}]} + } + } + } + } run_graphql_test(query, expected_output, graph) 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 dc56b658a0..579f4b3016 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 @@ -583,6 +583,7 @@ def test_edges_property_filter_ends_with(graph): EVENT_GRAPH = init_graph2(Graph()) PERSISTENT_GRAPH = init_graph2(PersistentGraph()) + @pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) def test_edges_selection(graph): query = """ @@ -594,12 +595,18 @@ def test_edges_selection(graph): } } """ - expected_output = {'graph': {'edges': {'list': [ - {'dst': {'name': '2'}, 'src': {'name': '1'}}, - {'dst': {'name': '1'}, 'src': {'name': '3'}}, - {'dst': {'name': '4'}, 'src': {'name': '3'}}, - {'dst': {'name': '1'}, 'src': {'name': '2'}}] - }}} + expected_output = { + "graph": { + "edges": { + "list": [ + {"dst": {"name": "2"}, "src": {"name": "1"}}, + {"dst": {"name": "1"}, "src": {"name": "3"}}, + {"dst": {"name": "4"}, "src": {"name": "3"}}, + {"dst": {"name": "1"}, "src": {"name": "2"}}, + ] + } + } + } run_graphql_test(query, expected_output, graph) @@ -619,12 +626,20 @@ def test_edges_selection_edges_filter_paired(graph): } } """ - expected_output = {'graph': {'edges': {'filter': {'list': [ - {'dst': {'name': '2'}, 'src': {'name': '1'}}, - {'dst': {'name': '1'}, 'src': {'name': '3'}}, - {'dst': {'name': '4'}, 'src': {'name': '3'}}, - {'dst': {'name': '1'}, 'src': {'name': '2'}}] - }}}} + expected_output = { + "graph": { + "edges": { + "filter": { + "list": [ + {"dst": {"name": "2"}, "src": {"name": "1"}}, + {"dst": {"name": "1"}, "src": {"name": "3"}}, + {"dst": {"name": "4"}, "src": {"name": "3"}}, + {"dst": {"name": "1"}, "src": {"name": "2"}}, + ] + } + } + } + } run_graphql_test(query, expected_output, graph) @@ -648,9 +663,15 @@ def test_edges_chained_selection_edges_filter_paired(graph): } } """ - expected_output = {'graph': {'edges': {'select': {'filter': {'list': [ - {'dst': {'name': '2'}, 'src': {'name': '1'}} - ]}}}}} + expected_output = { + "graph": { + "edges": { + "select": { + "filter": {"list": [{"dst": {"name": "2"}, "src": {"name": "1"}}]} + } + } + } + } run_graphql_test(query, expected_output, graph) @@ -676,7 +697,17 @@ def test_edges_chained_selection_edges_filter_paired_ver2(graph): } } """ - expected_output = {'graph': {'edges': {'select': {'select': {'filter': {'list': [ - {'dst': {'name': '2'}, 'src': {'name': '1'}} - ]}}}}}} + expected_output = { + "graph": { + "edges": { + "select": { + "select": { + "filter": { + "list": [{"dst": {"name": "2"}, "src": {"name": "1"}}] + } + } + } + } + } + } run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py index f2aca0df8d..4134933c5a 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_neighbours_filter.py @@ -1,6 +1,6 @@ import pytest from raphtory import Graph, PersistentGraph -from filters_setup import create_test_graph +from filters_setup import create_test_graph, init_graph2 from utils import run_graphql_test, run_graphql_error_test EVENT_GRAPH = create_test_graph(Graph()) @@ -171,3 +171,98 @@ def test_neighbours_not_found(graph): """ expected_output = {"graph": {"node": {"filter": {"neighbours": {"list": []}}}}} run_graphql_test(query, expected_output, graph) + + +EVENT_GRAPH = init_graph2(Graph()) +PERSISTENT_GRAPH = init_graph2(PersistentGraph()) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_neighbours_selection(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + list { + neighbours { + select(expr: { + property: { name: "p2", where: { gt: { i64: 3 } } } + }) { + list { + name + } + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": { + "list": [ + {"neighbours": {"select": {"list": [{"name": "3"}]}}}, + {"neighbours": {"select": {"list": []}}}, + ] + } + } + } + run_graphql_test(query, expected_output, graph) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_neighbours_neighbours_filtering(graph): + query = """ + query { + graph(path: "g") { + nodes(select: { property: { name: "p100", where: { gt: { i64: 30 } } } }) { + list { + neighbours { + filter(expr: { + property: { name: "p2", where: { gt: { i64: 3 } } } + }) { + list { + neighbours { + list { + name + } + } + } + } + } + } + } + } + } + """ + expected_output = { + "graph": { + "nodes": { + "list": [ + { + "neighbours": { + "filter": { + "list": [ + {"neighbours": {"list": [{"name": "3"}]}}, + {"neighbours": {"list": []}}, + ] + } + } + }, + { + "neighbours": { + "filter": { + "list": [ + {"neighbours": {"list": [{"name": "3"}]}}, + {"neighbours": {"list": [{"name": "3"}]}}, + {"neighbours": {"list": [{"name": "3"}]}}, + ] + } + } + }, + ] + } + } + } + run_graphql_test(query, expected_output, graph) diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index 7cfebb8d76..8e092ed05b 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -900,9 +900,7 @@ def test_nodes_selection(graph): } } """ - expected_output = { - "graph": {"nodes": {"list": [{"name": "1"}, {"name": "3"}]}} - } + expected_output = {"graph": {"nodes": {"list": [{"name": "1"}, {"name": "3"}]}}} run_graphql_test(query, expected_output, graph) @@ -952,10 +950,18 @@ def test_nodes_selection_nodes_filter_paired2(graph): } } """ - expected_output = {'graph': {'nodes': {'filter': {'list': [ - {'neighbours': {'list': []}}, - {'neighbours': {'list': [{'name': '1'}]}} - ]}}}} + expected_output = { + "graph": { + "nodes": { + "filter": { + "list": [ + {"neighbours": {"list": []}}, + {"neighbours": {"list": [{"name": "1"}]}}, + ] + } + } + } + } run_graphql_test(query, expected_output, graph) diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index f9404616fe..4bc9965c10 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -887,8 +887,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 { @@ -1771,7 +1771,7 @@ type Nodes { """ ids: [String!]! """ - Returns a view of the node types. + Returns a filtered view that applies to list down the chain """ filter(expr: NodeFilter!): Nodes! """ @@ -1978,6 +1978,14 @@ type PathFromNode { Takes a specified selection of views and applies them in given order. """ applyViews(views: [PathFromNodeViewCollection!]!): PathFromNode! + """ + Returns a filtered view that applies to list down the chain + """ + filter(expr: NodeFilter!): PathFromNode! + """ + Returns filtered list of neighbour nodes + """ + select(expr: NodeFilter!): PathFromNode! } input PathFromNodeViewCollection @oneOf { diff --git a/raphtory-graphql/src/model/graph/edges.rs b/raphtory-graphql/src/model/graph/edges.rs index 5ff1baa3a6..fcdb4d99f0 100644 --- a/raphtory-graphql/src/model/graph/edges.rs +++ b/raphtory-graphql/src/model/graph/edges.rs @@ -352,6 +352,7 @@ impl GqlEdges { blocking_compute(move || self_clone.iter().collect()).await } + /// Returns a filtered view that applies to list down the chain async fn filter(&self, expr: EdgeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { @@ -362,6 +363,7 @@ impl GqlEdges { .await } + /// Returns filtered list of edges async fn select(&self, expr: EdgeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index 811f7329d3..1a462c3a98 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -342,7 +342,7 @@ impl GqlNodes { blocking_compute(move || self_clone.nn.name().collect()).await } - /// Returns a view of the node types. + /// Returns a filtered view that applies to list down the chain async fn filter(&self, expr: NodeFilter) -> Result { let self_clone = self.clone(); blocking_compute(move || { diff --git a/raphtory-graphql/src/model/graph/path_from_node.rs b/raphtory-graphql/src/model/graph/path_from_node.rs index d077a69c02..cbe1617b91 100644 --- a/raphtory-graphql/src/model/graph/path_from_node.rs +++ b/raphtory-graphql/src/model/graph/path_from_node.rs @@ -1,6 +1,6 @@ use crate::{ model::graph::{ - filtering::PathFromNodeViewCollection, + filtering::{NodeFilter, PathFromNodeViewCollection}, node::GqlNode, windowset::GqlPathFromNodeWindowSet, WindowDuration::{self, Duration, Epoch}, @@ -9,7 +9,10 @@ use crate::{ }; use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ - db::{api::view::DynamicGraph, graph::path::PathFromNode}, + db::{ + api::view::{BaseFilterOps, DynamicGraph, IterFilterOps}, + graph::{path::PathFromNode, views::filter::model::node_filter::CompositeNodeFilter}, + }, errors::GraphError, prelude::*, }; @@ -268,4 +271,26 @@ impl GqlPathFromNode { } Ok(return_view) } + + /// Returns a filtered view that applies to list down the chain + async fn filter(&self, expr: NodeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeNodeFilter = expr.try_into()?; + let filtered = self_clone.nn.filter(filter)?; + Ok(self_clone.update(filtered.into_dyn())) + }) + .await + } + + /// Returns filtered list of neighbour nodes + async fn select(&self, expr: NodeFilter) -> Result { + let self_clone = self.clone(); + blocking_compute(move || { + let filter: CompositeNodeFilter = expr.try_into()?; + let filtered = self_clone.nn.filter_iter(filter)?; + Ok(self_clone.update(filtered.into_dyn())) + }) + .await + } } diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index 37b626e664..c92ac38107 100644 --- a/raphtory/src/db/graph/path.rs +++ b/raphtory/src/db/graph/path.rs @@ -275,6 +275,15 @@ pub struct PathFromNode<'graph, G> { pub(crate) op: Arc BoxedLIter<'graph, VID> + Send + Sync + 'graph>, } +impl<'graph, G: IntoDynamic> PathFromNode<'graph, G> { + pub fn into_dyn(self) -> PathFromNode<'graph, DynamicGraph> { + PathFromNode { + base_graph: self.base_graph.into_dynamic(), + op: self.op, + } + } +} + impl<'graph, G: GraphViewOps<'graph>> PathFromNode<'graph, G> { pub(crate) fn new BoxedLIter<'graph, VID> + Send + Sync + 'graph>( graph: G, 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 6c791696b7..67723bd630 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -23,7 +23,7 @@ use crate::{ }, }, errors::GraphError, - prelude::{GraphViewOps, PropertiesOps, TimeOps}, + prelude::{GraphViewOps, PropertiesOps}, }; use itertools::Itertools; use raphtory_api::core::{ @@ -1258,7 +1258,6 @@ impl PropertyFilter { } Op::Any => { for p in &seq { - saw = true; let ok = if elem_quals.is_empty() { pred(p) } else { From fd1253a422e03a99ca40465e55e679767f90d8a5 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Sun, 26 Oct 2025 21:51:31 +0000 Subject: [PATCH 11/42] impl window filter --- .../src/db/api/properties/temporal_props.rs | 13 + raphtory/src/db/graph/views/filter/mod.rs | 247 ++++++++++++- .../graph/views/filter/model/edge_filter.rs | 16 +- .../src/db/graph/views/filter/model/mod.rs | 63 +++- .../graph/views/filter/model/node_filter.rs | 7 +- .../views/filter/model/property_filter.rs | 325 ++++++++++++++++-- 6 files changed, 630 insertions(+), 41 deletions(-) diff --git a/raphtory/src/db/api/properties/temporal_props.rs b/raphtory/src/db/api/properties/temporal_props.rs index f4936d2b66..16962c15fb 100644 --- a/raphtory/src/db/api/properties/temporal_props.rs +++ b/raphtory/src/db/api/properties/temporal_props.rs @@ -13,6 +13,7 @@ use std::{ sync::Arc, }; +use raphtory_api::core::storage::timeindex::AsTime; #[cfg(feature = "arrow")] use {arrow_array::ArrayRef, raphtory_api::core::entities::properties::prop::PropArrayUnwrap}; @@ -91,6 +92,18 @@ impl TemporalPropertyView

{ self.props.temporal_iter(self.id) } + #[inline] + pub fn iter_window(&self, start: i64, end: i64) -> impl Iterator + '_ { + self.iter_indexed() + .filter(move |(ti, _)| ti.t() >= start && ti.t() < end) + .map(|(ti, p)| (ti.t(), p)) + } + + #[inline] + pub fn values_window(&self, start: i64, end: i64) -> impl Iterator + '_ { + self.iter_window(start, end).map(|(_, p)| p) + } + pub fn histories(&self) -> impl Iterator + '_ { self.iter() } diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index d0ee6d4f5c..e16fc78e97 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -2612,10 +2612,6 @@ pub(crate) mod test_filters { #[cfg(test)] mod test_node_property_filter { - use crate::db::graph::views::filter::test_filters::init_nodes_graph; - use raphtory_api::core::entities::properties::prop::Prop; - use std::vec; - use crate::db::graph::{ assertions::{assert_filter_nodes_results, assert_search_nodes_results, TestVariants}, views::filter::{ @@ -2625,9 +2621,11 @@ pub(crate) mod test_filters { property_filter::{ListAggOps, PropertyFilterOps}, ComposableFilter, PropertyFilterFactory, }, - test_filters::IdentityGraphTransformer, + test_filters::{init_nodes_graph, IdentityGraphTransformer}, }, }; + use raphtory_api::core::entities::properties::prop::Prop; + use std::vec; #[test] fn test_exact_match() { @@ -3807,6 +3805,124 @@ pub(crate) mod test_filters { TestVariants::All, ); } + + #[test] + fn test_nodes_window_filter() { + let filter = NodeFilter::window(1, 3) + .property("p2") + .temporal() + .sum() + .ge(2u64); + + let expected_results = vec!["2"]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + + // Wider window includes node 3 + let filter = NodeFilter::window(1, 5) + .property("p2") + .temporal() + .sum() + .ge(2u64); + + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_nodes_window_filter_on_non_temporal_property_has_no_effect() { + // p1 is used as latest (non-temporal); window should not change outcome + let filter1 = NodeFilter::window(1, 2).property("p1").eq("shivam_kapoor"); + let filter2 = NodeFilter::window(100, 200) + .property("p1") + .eq("shivam_kapoor"); + + let expected_results = vec!["1"]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter1.clone(), + &expected_results, + TestVariants::All, + ); + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter2.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_nodes_window_filter_any_all_over_window() { + let filter = NodeFilter::window(3, 5) + .property("p20") + .temporal() + .any() + .eq("Gold_boat"); + + let expected_results = vec!["4"]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + + let filter = NodeFilter::window(3, 5) + .property("p20") + .temporal() + .all() + .eq("Gold_boat"); + + let expected_results = vec![]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_nodes_window_filter_and() { + // Filters both node 1 and 3 + let filter1 = NodeFilter::window(1, 4) + .property("p10") + .temporal() + .any() + .eq("Paper_airplane"); + + // Filters only node 3 + let filter2 = NodeFilter::window(3, 6) + .property("p2") + .temporal() + .sum() + .eq(6u64); + + let expected_results = vec!["3"]; + assert_filter_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter1.and(filter2).clone(), + &expected_results, + TestVariants::All, + ); + } } #[cfg(test)] @@ -9815,6 +9931,127 @@ pub(crate) mod test_filters { TestVariants::EventOnly, ); } + + #[test] + fn test_edges_window_filter() { + let filter = EdgeFilter::window(1, 3) + .property("p2") + .temporal() + .sum() + .ge(2u64); + + let expected_results = vec!["1->2", "2->3"]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + + let filter = EdgeFilter::window(1, 5) + .property("p2") + .temporal() + .sum() + .ge(2u64); + + let expected_results = vec![ + "1->2", + "2->3", + "3->1", + "2->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_edges_window_filter_on_non_temporal_property_has_no_effect() { + 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, + filter1.clone(), + &expected_results, + TestVariants::All, + ); + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter2.clone(), + &expected_results, + TestVariants::All, + ); + } + + #[test] + fn test_edges_window_filter_any_all_over_window() { + let filter_any = EdgeFilter::window(2, 4) + .property("p20") + .temporal() + .any() + .eq("Gold_boat"); + + let expected_any = vec!["2->3"]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter_any.clone(), + &expected_any, + TestVariants::All, + ); + + let filter_all = EdgeFilter::window(2, 4) + .property("p20") + .temporal() + .all() + .eq("Gold_boat"); + + let expected_all: Vec<&str> = vec![]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter_all.clone(), + &expected_all, + TestVariants::All, + ); + } + + #[test] + fn test_edges_window_filter_and() { + let filter1 = EdgeFilter::window(3, 6) + .property("p10") + .temporal() + .any() + .eq("Paper_airplane"); + + let filter2 = EdgeFilter::window(3, 6) + .property("p2") + .temporal() + .sum() + .eq(6u64); + + let expected_results = vec!["2->1"]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter1.and(filter2).clone(), + &expected_results, + TestVariants::All, + ); + } } #[cfg(test)] 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 01a494e5ec..454b7bd60f 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -5,14 +5,15 @@ use crate::{ internal::CreateFilter, model::{ node_filter::CompositeNodeFilter, property_filter::PropertyFilter, AndFilter, - Filter, NotFilter, OrFilter, PropertyFilterFactory, TryAsCompositeFilter, + Filter, NotFilter, OrFilter, PropertyFilterFactory, TryAsCompositeFilter, Windowed, }, }, }, errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::entities::GID; +use raphtory_api::core::{entities::GID, storage::timeindex::TimeIndexEntry}; +use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; #[derive(Debug, Clone)] @@ -373,9 +374,14 @@ impl EdgeFilter { pub fn src() -> EdgeEndpointFilter { EdgeEndpointFilter::Src } + pub fn dst() -> EdgeEndpointFilter { EdgeEndpointFilter::Dst } + + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end) + } } impl PropertyFilterFactory for EdgeFilter {} @@ -385,6 +391,12 @@ pub struct ExplodedEdgeFilter; impl PropertyFilterFactory for ExplodedEdgeFilter {} +impl ExplodedEdgeFilter { + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end) + } +} + impl TryAsCompositeFilter for EdgeFieldFilter { fn try_as_composite_node_filter(&self) -> Result { Err(GraphError::NotSupported) diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 3d934244c8..1e904b929d 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -6,14 +6,21 @@ use crate::{ node_filter::{CompositeNodeFilter, NodeNameFilter, NodeTypeFilter}, not_filter::NotFilter, or_filter::OrFilter, - property_filter::{MetadataFilterBuilder, PropertyFilter, PropertyFilterBuilder}, + property_filter::{ + MetadataFilterBuilder, PropertyFilter, PropertyFilterBuilder, PropertyRef, + WindowedPropertyRef, + }, }, errors::GraphError, prelude::{GraphViewOps, NodeViewOps}, }; -use raphtory_api::core::entities::{GidRef, GID}; +use raphtory_api::core::{ + entities::{GidRef, GID}, + storage::timeindex::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, ops::Deref, sync::Arc}; +use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Deref, sync::Arc}; pub mod and_filter; pub mod edge_filter; @@ -23,6 +30,56 @@ pub mod not_filter; pub mod or_filter; pub mod property_filter; +#[derive(Debug, Clone, Copy)] +pub struct Windowed { + pub start: TimeIndexEntry, + pub end: TimeIndexEntry, + pub _marker: PhantomData, +} + +impl Windowed { + #[inline] + pub fn new(start: TimeIndexEntry, end: TimeIndexEntry) -> Self { + Self { + start, + end, + _marker: PhantomData, + } + } + + #[inline] + pub fn from_times(start: S, end: E) -> Self { + let s = TimeIndexEntry::start(start.into_time()); + let e = TimeIndexEntry::end(end.into_time()); + Self::new(s, e) + } +} + +impl Windowed +where + M: Send + Sync + Clone + 'static, +{ + pub fn property(self, name: impl Into) -> WindowedPropertyRef { + WindowedPropertyRef { + prop_ref: PropertyRef::Property(name.into()), + ops: vec![], + start: self.start, + end: self.end, + _phantom: PhantomData, + } + } + + pub fn metadata(self, name: impl Into) -> WindowedPropertyRef { + WindowedPropertyRef { + prop_ref: PropertyRef::Metadata(name.into()), + ops: vec![], + start: self.start, + end: self.end, + _phantom: PhantomData, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum FilterValue { Single(String), 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 2c2326d9db..2e593844e8 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -8,7 +8,7 @@ use crate::{ filter_operator::FilterOperator, property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, OrFilter, PropertyFilterFactory, - TryAsCompositeFilter, + TryAsCompositeFilter, Windowed, }, }, }, @@ -16,6 +16,7 @@ use crate::{ prelude::GraphViewOps, }; use raphtory_api::core::entities::{GidType, GID}; +use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; #[derive(Debug, Clone)] @@ -308,6 +309,10 @@ impl NodeFilter { NodeTypeFilterBuilder } + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end) + } + pub fn validate(id_dtype: Option, filter: &Filter) -> Result<(), GraphError> { use FilterOperator::*; use GidType::*; 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 67723bd630..c29479c0f0 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -3,7 +3,7 @@ use crate::{ api::{ properties::{internal::InternalPropertiesOps, Metadata, Properties}, view::{ - internal::{GraphView, NodeTimeSemanticsOps}, + internal::{GraphTimeSemanticsOps, GraphView, NodeTimeSemanticsOps}, node::NodeViewOps, EdgeViewOps, }, @@ -23,7 +23,7 @@ use crate::{ }, }, errors::GraphError, - prelude::{GraphViewOps, PropertiesOps}, + prelude::{GraphViewOps, PropertiesOps, TimeOps}, }; use itertools::Itertools; use raphtory_api::core::{ @@ -34,11 +34,17 @@ use raphtory_api::core::{ }, EID, }, - storage::{arc_str::ArcStr, timeindex::TimeIndexEntry}, + storage::{ + arc_str::ArcStr, + timeindex::{AsTime, TimeIndexEntry}, + }, }; -use raphtory_storage::graph::{ - edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, - nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, +use raphtory_storage::{ + core_ops::CoreGraphOps, + graph::{ + edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + }, }; use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Deref, sync::Arc}; @@ -168,6 +174,7 @@ pub struct PropertyFilter { pub prop_value: PropertyFilterValue, pub operator: FilterOperator, pub ops: Vec, // validated by validate_chain_and_infer_effective_dtype + pub window: Option<(TimeIndexEntry, TimeIndexEntry)>, pub _phantom: PhantomData, } @@ -223,12 +230,18 @@ impl PropertyFilter { self } + pub fn with_window(mut self, start: TimeIndexEntry, end: TimeIndexEntry) -> Self { + self.window = Some((start, end)); + self + } + pub fn eq(prop_ref: PropertyRef, prop_value: impl Into) -> Self { Self { prop_ref, prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Eq, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -239,6 +252,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Ne, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -249,6 +263,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Le, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -259,6 +274,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Ge, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -269,6 +285,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Lt, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -279,6 +296,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Gt, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -289,6 +307,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), operator: FilterOperator::IsIn, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -299,6 +318,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), operator: FilterOperator::IsNotIn, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -309,6 +329,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::None, operator: FilterOperator::IsNone, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -319,6 +340,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::None, operator: FilterOperator::IsSome, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -329,6 +351,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::StartsWith, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -339,6 +362,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::EndsWith, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -349,6 +373,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::Contains, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -359,6 +384,7 @@ impl PropertyFilter { prop_value: PropertyFilterValue::Single(prop_value.into()), operator: FilterOperator::NotContains, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -377,6 +403,7 @@ impl PropertyFilter { prefix_match, }, ops: vec![], + window: None, _phantom: PhantomData, } } @@ -1327,8 +1354,12 @@ impl PropertyFilter { let Some(tview) = props.temporal().get_by_id(t_prop_id) else { return false; }; - let props: Vec = tview.values().collect(); - self.eval_temporal_and_apply(props) + let seq = if let Some((start, end)) = self.window { + tview.values_window(start.t(), end.t()).collect() + } else { + tview.values().collect() + }; + self.eval_temporal_and_apply(seq) } } } @@ -1369,10 +1400,14 @@ impl PropertyFilter { PropertyRef::TemporalProperty(_) => { let props = node_view.properties(); let Some(tview) = props.temporal().get_by_id(prop_id) else { - return false; // no temporal values at all for this node/property + return false; }; - let seq: Vec = tview.values().collect(); + let seq = if let Some((start, end)) = self.window { + tview.values_window(start.t(), end.t()).collect() + } else { + tview.values().collect() + }; if self.ops.is_empty() { return self.eval_temporal_and_apply(seq); @@ -1418,6 +1453,23 @@ impl PropertyFilter { self.is_metadata_matched(prop_id, props) } PropertyRef::TemporalProperty(_) | PropertyRef::Property(_) => { + if matches!(self.prop_ref, PropertyRef::TemporalProperty(_)) { + let seq: Vec = if let Some((start, end)) = self.window { + edge.properties() + .temporal() + .get_by_id(prop_id) + .map(|tv| tv.values_window(start.t(), end.t()).collect()) + .unwrap_or_default() + } else { + edge.properties() + .temporal() + .get_by_id(prop_id) + .map(|tv| tv.values().collect()) + .unwrap_or_default() + }; + return self.eval_temporal_and_apply(seq); + } + let props = edge.properties(); self.is_property_matched(prop_id, props) } @@ -1502,16 +1554,26 @@ pub trait InternalPropertyFilterOps: Send + Sync { fn ops(&self) -> &[Op] { &[] } + + fn window(&self) -> Option<(TimeIndexEntry, TimeIndexEntry)> { + None + } } impl InternalPropertyFilterOps for Arc { type Marker = T::Marker; + fn property_ref(&self) -> PropertyRef { self.deref().property_ref() } + fn ops(&self) -> &[Op] { self.deref().ops() } + + fn window(&self) -> Option<(TimeIndexEntry, TimeIndexEntry)> { + self.deref().window() + } } pub trait PropertyFilterOps: InternalPropertyFilterOps { @@ -1539,64 +1601,161 @@ pub trait PropertyFilterOps: InternalPropertyFilterOps { impl PropertyFilterOps for T { fn eq(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::eq(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::eq(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn ne(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::ne(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::ne(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn le(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::le(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::le(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn ge(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::ge(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::ge(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn lt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::lt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::lt(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn gt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::gt(self.property_ref(), value.into()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::gt(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter::is_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) + let pf = + PropertyFilter::is_in(self.property_ref(), values).with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter::is_not_in(self.property_ref(), values).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::is_not_in(self.property_ref(), values) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn is_none(&self) -> PropertyFilter { - PropertyFilter::is_none(self.property_ref()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::is_none(self.property_ref()).with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn is_some(&self) -> PropertyFilter { - PropertyFilter::is_some(self.property_ref()).with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::is_some(self.property_ref()).with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn starts_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::starts_with(self.property_ref(), value.into()) - .with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::starts_with(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn ends_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::ends_with(self.property_ref(), value.into()) - .with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::ends_with(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::contains(self.property_ref(), value.into()) - .with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::contains(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn not_contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter::not_contains(self.property_ref(), value.into()) - .with_ops(self.ops().iter().copied()) + let pf = PropertyFilter::not_contains(self.property_ref(), value.into()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } + fn fuzzy_search( &self, prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, ) -> PropertyFilter { - PropertyFilter::fuzzy_search( + let pf = PropertyFilter::fuzzy_search( self.property_ref(), prop_value.into(), levenshtein_distance, prefix_match, ) - .with_ops(self.ops().iter().copied()) + .with_ops(self.ops().iter().copied()); + if let Some((s, e)) = self.window() { + pf.with_window(s, e) + } else { + pf + } } } @@ -1653,27 +1812,35 @@ impl OpChainBuilder { pub fn first(self) -> Self { self.with_op(Op::First) } + pub fn last(self) -> Self { self.with_op(Op::Last) } + pub fn any(self) -> Self { self.with_op(Op::Any) } + pub fn all(self) -> Self { self.with_op(Op::All) } + pub fn len(self) -> Self { self.with_op(Op::Len) } + pub fn sum(self) -> Self { self.with_op(Op::Sum) } + pub fn avg(self) -> Self { self.with_op(Op::Avg) } + pub fn min(self) -> Self { self.with_op(Op::Min) } + pub fn max(self) -> Self { self.with_op(Op::Max) } @@ -1681,9 +1848,11 @@ impl OpChainBuilder { impl InternalPropertyFilterOps for OpChainBuilder { type Marker = M; + fn property_ref(&self) -> PropertyRef { self.prop_ref.clone() } + fn ops(&self) -> &[Op] { &self.ops } @@ -1732,6 +1901,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } + fn sum(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1739,6 +1909,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } + fn avg(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1746,6 +1917,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } + fn min(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1753,6 +1925,7 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { _phantom: PhantomData, } } + fn max(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1762,3 +1935,95 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } impl ListAggOps for T {} + +#[derive(Clone)] +pub struct WindowedPropertyRef { + pub prop_ref: PropertyRef, + pub ops: Vec, + pub start: TimeIndexEntry, + pub end: TimeIndexEntry, + pub _phantom: PhantomData, +} + +impl InternalPropertyFilterOps for WindowedPropertyRef +where + M: Send + Sync + Clone + 'static, +{ + type Marker = M; + + fn property_ref(&self) -> PropertyRef { + self.prop_ref.clone() + } + + fn ops(&self) -> &[Op] { + &self.ops + } + + fn window(&self) -> Option<(TimeIndexEntry, TimeIndexEntry)> { + Some((self.start, self.end)) + } +} + +impl WindowedPropertyRef { + #[inline] + pub fn temporal(mut self) -> Self { + if let PropertyRef::Property(name) = self.prop_ref { + self.prop_ref = PropertyRef::TemporalProperty(name); + } + self + } + + #[inline] + pub fn any(mut self) -> Self { + self.ops.push(Op::Any); + self + } + + #[inline] + pub fn all(mut self) -> Self { + self.ops.push(Op::All); + self + } + + #[inline] + pub fn len(mut self) -> Self { + self.ops.push(Op::Len); + self + } + + #[inline] + pub fn sum(mut self) -> Self { + self.ops.push(Op::Sum); + self + } + + #[inline] + pub fn avg(mut self) -> Self { + self.ops.push(Op::Avg); + self + } + + #[inline] + pub fn min(mut self) -> Self { + self.ops.push(Op::Min); + self + } + + #[inline] + pub fn max(mut self) -> Self { + self.ops.push(Op::Max); + self + } + + #[inline] + pub fn first(mut self) -> Self { + self.ops.push(Op::First); + self + } + + #[inline] + pub fn last(mut self) -> Self { + self.ops.push(Op::Last); + self + } +} From 520ad5ceec07bb0d40e55f1793cecdbb2ede98b5 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:16:55 +0000 Subject: [PATCH 12/42] impl window filter in python, add tests --- python/python/raphtory/filter/__init__.pyi | 22 ++++ raphtory-graphql/schema.graphql | 9 +- .../src/python/filter/edge_filter_builders.rs | 34 +++++- raphtory/src/python/filter/mod.rs | 5 + .../src/python/filter/node_filter_builders.rs | 22 +++- .../python/filter/property_filter_builders.rs | 114 +++++++++++++++++- raphtory/src/python/filter/window_filter.rs | 66 ++++++++++ 7 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 raphtory/src/python/filter/window_filter.rs diff --git a/python/python/raphtory/filter/__init__.pyi b/python/python/raphtory/filter/__init__.pyi index 009c7c5670..e0fd7871c0 100644 --- a/python/python/raphtory/filter/__init__.pyi +++ b/python/python/raphtory/filter/__init__.pyi @@ -32,6 +32,9 @@ __all__ = [ "ExplodedEdge", "Property", "Metadata", + "NodeWindow", + "EdgeWindow", + "ExplodedEdgeWindow", ] class FilterExpr(object): @@ -87,6 +90,7 @@ class PropertyFilterOps(object): def not_contains(self, value): ... def starts_with(self, value): ... def sum(self): ... + def temporal(self): ... class Node(object): @staticmethod @@ -120,6 +124,8 @@ class Node(object): @staticmethod def property(name): ... + @staticmethod + def window(py_start, py_end): ... class EdgeFilterOp(object): def __eq__(self, value): @@ -161,14 +167,30 @@ class Edge(object): def property(name): ... @staticmethod def src(): ... + @staticmethod + def window(py_start, py_end): ... class ExplodedEdge(object): @staticmethod def metadata(name): ... @staticmethod def property(name): ... + @staticmethod + def window(py_start, py_end): ... class Property(PropertyFilterOps): def temporal(self): ... class Metadata(PropertyFilterOps): ... + +class NodeWindow(object): + def metadata(self, name): ... + def property(self, name): ... + +class EdgeWindow(object): + def metadata(self, name): ... + def property(self, name): ... + +class ExplodedEdgeWindow(object): + def metadata(self, name): ... + def property(self, name): ... diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index 4bc9965c10..4488e16b05 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -583,7 +583,13 @@ type Edges { Returns a list of all objects in the current selection of the collection. You should filter filter the collection first then call list. """ list: [Edge!]! + """ + Returns a filtered view that applies to list down the chain + """ filter(expr: EdgeFilter!): Edges! + """ + Returns filtered list of edges + """ select(expr: EdgeFilter!): Edges! } @@ -887,8 +893,8 @@ type Graph { } type GraphAlgorithmPlugin { - pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! shortest_path(source: String!, targets: [String!]!, direction: String): [ShortestPathOutput!]! + pagerank(iterCount: Int!, threads: Int, tol: Float): [PagerankOutput!]! } type GraphSchema { @@ -2117,6 +2123,7 @@ type Property { input PropertyFilterNew { name: String! + window: Window where: PropCondition! } diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 055abe5501..cc7a42d0cc 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -5,11 +5,17 @@ use crate::{ InternalEdgeFilterBuilderOps, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - PropertyFilterFactory, + PropertyFilterFactory, Windowed, + }, + python::{ + filter::{ + filter_expr::PyFilterExpr, + window_filter::{py_into_millis, PyEdgeWindow, PyExplodedEdgeWindow}, + }, + types::iterable::FromIterable, }, - python::{filter::filter_expr::PyFilterExpr, types::iterable::FromIterable}, }; -use pyo3::{pyclass, pymethods}; +use pyo3::{exceptions::PyTypeError, pyclass, pymethods, Bound, PyAny, PyResult}; use raphtory_api::core::entities::GID; use std::sync::Arc; @@ -186,6 +192,16 @@ impl PyEdgeFilter { fn metadata(name: String) -> MetadataFilterBuilder { EdgeFilter::metadata(name) } + + #[staticmethod] + fn window(py_start: Bound, py_end: Bound) -> PyResult { + let s = py_into_millis(&py_start)?; + let e = py_into_millis(&py_end)?; + if s > e { + return Err(PyTypeError::new_err("window.start must be <= window.end")); + } + Ok(PyEdgeWindow(Windowed::::from_times(s, e))) + } } #[pyclass(frozen, name = "ExplodedEdge", module = "raphtory.filter")] @@ -203,4 +219,16 @@ impl PyExplodedEdgeFilter { fn metadata(name: String) -> MetadataFilterBuilder { ExplodedEdgeFilter::metadata(name) } + + #[staticmethod] + fn window(py_start: Bound, py_end: Bound) -> PyResult { + let s = py_into_millis(&py_start)?; + let e = py_into_millis(&py_end)?; + if s > e { + return Err(PyTypeError::new_err("window.start must be <= window.end")); + } + Ok(PyExplodedEdgeWindow( + Windowed::::from_times(s, e), + )) + } } diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 2a896ec88c..59e418087d 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -5,6 +5,7 @@ use crate::python::filter::{ property_filter_builders::{ PyMetadataFilterBuilder, PyPropertyFilterBuilder, PyPropertyFilterOps, }, + window_filter::{PyEdgeWindow, PyExplodedEdgeWindow, PyNodeWindow}, }; use pyo3::{ prelude::{PyModule, PyModuleMethods}, @@ -16,6 +17,7 @@ pub mod edge_filter_builders; 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")?; @@ -28,6 +30,9 @@ pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { 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 297cfc7c05..e33042a710 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -4,11 +4,17 @@ use crate::{ InternalNodeFilterBuilderOps, NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - PropertyFilterFactory, + PropertyFilterFactory, Windowed, + }, + python::{ + filter::{ + filter_expr::PyFilterExpr, + window_filter::{py_into_millis, PyNodeWindow}, + }, + types::iterable::FromIterable, }, - python::{filter::filter_expr::PyFilterExpr, types::iterable::FromIterable}, }; -use pyo3::{pyclass, pymethods}; +use pyo3::{exceptions::PyTypeError, pyclass, pymethods, Bound, PyAny, PyResult}; use raphtory_api::core::entities::GID; use std::sync::Arc; @@ -177,6 +183,16 @@ impl PyNodeFilter { fn metadata(name: String) -> MetadataFilterBuilder { NodeFilter::metadata(name) } + + #[staticmethod] + fn window(py_start: Bound, py_end: Bound) -> PyResult { + let s = py_into_millis(&py_start)?; + let e = py_into_millis(&py_end)?; + if s > e { + return Err(PyTypeError::new_err("window.start must be <= window.end")); + } + Ok(PyNodeWindow(Windowed::::from_times(s, e))) + } } pub trait DynNodeFilterBuilderOps: Send + Sync { diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index f44729b590..b8be34024d 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -4,7 +4,7 @@ use crate::{ model::{ property_filter::{ ElemQualifierOps, ListAggOps, MetadataFilterBuilder, OpChainBuilder, - PropertyFilterBuilder, PropertyFilterOps, + PropertyFilterBuilder, PropertyFilterOps, WindowedPropertyRef, }, TryAsCompositeFilter, }, @@ -582,7 +582,7 @@ impl PyPropertyFilterOps { Self { ops: Arc::new(t) } } - fn from_arc(ops: Arc) -> Self { + pub fn from_arc(ops: Arc) -> Self { Self { ops } } } @@ -690,6 +690,10 @@ impl PyPropertyFilterOps { fn max(&self) -> PyResult { self.ops.max() } + + pub fn temporal(&self) -> PyResult { + self.ops.temporal() + } } trait DynPropertyFilterBuilderOps: DynFilterOps { @@ -761,3 +765,109 @@ where Bound::new(py, (child, parent)) } } + +impl DynFilterOps for WindowedPropertyRef +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn __eq__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + } + + fn __ne__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + } + + fn __lt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + } + + fn __le__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + } + + fn __gt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + } + + fn __ge__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + } + + fn is_none(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + } + + fn is_some(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + } + + fn starts_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, v))) + } + + fn ends_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, v))) + } + + fn contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, v))) + } + + fn not_contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, v))) + } + + fn fuzzy_search(&self, s: String, d: usize, p: bool) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search(self, s, d, p))) + } + + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().any())) + } + + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().all())) + } + + fn len(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().len())) + } + + fn sum(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().sum())) + } + + fn avg(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().avg())) + } + + fn min(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().min())) + } + + fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().max())) + } + + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().first())) + } + + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().last())) + } + + fn temporal(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().temporal())) + } +} diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs new file mode 100644 index 0000000000..140061b6dd --- /dev/null +++ b/raphtory/src/python/filter/window_filter.rs @@ -0,0 +1,66 @@ +use crate::{ + db::graph::views::filter::model::{ + edge_filter::{EdgeFilter, ExplodedEdgeFilter}, + node_filter::NodeFilter, + property_filter::WindowedPropertyRef, + Windowed, + }, + python::filter::property_filter_builders::PyPropertyFilterOps, +}; +use pyo3::prelude::*; +use std::sync::Arc; + +pub fn py_into_millis(obj: &Bound) -> PyResult { + obj.extract::() +} + +#[pyclass(frozen, name = "NodeWindow", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyNodeWindow(pub Windowed); + +#[pymethods] +impl PyNodeWindow { + fn property(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().property(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().metadata(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } +} + +#[pyclass(frozen, name = "EdgeWindow", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyEdgeWindow(pub Windowed); + +#[pymethods] +impl PyEdgeWindow { + fn property(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().property(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().metadata(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } +} + +#[pyclass(frozen, name = "ExplodedEdgeWindow", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyExplodedEdgeWindow(pub Windowed); + +#[pymethods] +impl PyExplodedEdgeWindow { + fn property(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().property(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.0.clone().metadata(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } +} From c4f388b242b62c08cac759e532a4d0d15d2777c8 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:24:10 +0000 Subject: [PATCH 13/42] impl gql window filter, add tests --- .../test_filters/test_node_property_filter.py | 40 ++++++++++++++ .../test_nodes_property_filter.py | 28 ++++++++++ raphtory-graphql/src/model/graph/filtering.rs | 54 +++++++++++++++++-- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/python/tests/test_base_install/test_filters/test_node_property_filter.py b/python/tests/test_base_install/test_filters/test_node_property_filter.py index 37527ad0d7..de8cbfccef 100644 --- a/python/tests/test_base_install/test_filters/test_node_property_filter.py +++ b/python/tests/test_base_install/test_filters/test_node_property_filter.py @@ -1104,3 +1104,43 @@ def check(graph): graph.filter(filter_expr).nodes.id return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_filter_nodes_temporal_window_sum_ge(): + def check(graph): + expr = filter.Node.window(1, 2).property("prop5").temporal().last().sum() >= 12 + assert sorted(graph.filter(expr).nodes.id) == ["c"] + + expr = filter.Node.window(1, 2).property("prop5").temporal().last().sum() >= 6 + assert sorted(graph.filter(expr).nodes.id) == ["a", "c"] + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_filter_nodes_two_windows_and(): + def check(graph): + filter1 = ( + filter.Node.window(1, 2).property("prop5").temporal().first().sum() == 6 + ) + filter2 = ( + filter.Node.window(2, 3).property("prop6").temporal().last().sum() == 12 + ) + assert sorted(graph.filter(filter1 & filter2).nodes.id) == ["a"] + + return check + + +@with_disk_variants(create_test_graph, variants=("graph", "persistent_graph")) +def test_filter_nodes_window_out_of_range_is_empty(): + def check(graph): + expr = filter.Node.window(10, 20).property("prop5").temporal().sum() >= 0 + assert list(graph.filter(expr).nodes.id) == [] + + return check + + +def test_filter_nodes_window_start_must_be_less_than_end(): + with pytest.raises(Exception, match="window.start must be <= window.end"): + filter.Node.window(3, 1).property("prop5").temporal().sum() >= 0 diff --git a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py index 8e092ed05b..c281ee5a22 100644 --- a/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_filters/test_nodes_property_filter.py @@ -1023,3 +1023,31 @@ def test_nodes_chained_selection_node_filter_paired_ver2(graph): } } run_graphql_test(query, expected_output, graph) + + +EVENT_GRAPH = create_test_graph(Graph()) +PERSISTENT_GRAPH = create_test_graph(PersistentGraph()) + + +@pytest.mark.parametrize("graph", [EVENT_GRAPH, PERSISTENT_GRAPH]) +def test_nodes_temporal_property_filter_any_avg_with_window(graph): + query = """ + query { + graph(path: "g") { + filterNodes(expr: { + temporalProperty: { + name: "prop5" + window: { start: 1, end: 3 } + where: { any: { avg: { lt: { f64: 10.0 } } } } + } + }) { + nodes { list { name } } + } + } + } + """ + + expected = { + "graph": {"filterNodes": {"nodes": {"list": [{"name": "a"}, {"name": "c"}]}}} + } + run_graphql_test(query, expected, graph) diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index fcb5d1d08a..8f8188e323 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -16,7 +16,10 @@ use raphtory::{ }, errors::GraphError, }; -use raphtory_api::core::entities::{properties::prop::Prop, GID}; +use raphtory_api::core::{ + entities::{properties::prop::Prop, GID}, + storage::timeindex::TimeIndexEntry, +}; use std::{ borrow::Cow, fmt, @@ -285,6 +288,7 @@ impl Display for NodeField { #[derive(InputObject, Clone, Debug)] pub struct PropertyFilterNew { pub name: String, + pub window: Option, #[graphql(name = "where")] pub where_: PropCondition, } @@ -856,6 +860,7 @@ fn build_property_filter_from_condition( prop_value, operator, ops, + window: None, _phantom: PhantomData, }) } @@ -918,16 +923,38 @@ impl TryFrom for CompositeNodeFilter { })) } NodeFilter::Property(prop) => { + if prop.window.is_some() { + return Err(GraphError::InvalidGqlFilter( + "window is only valid for TemporalProperty".into(), + )); + } let prop_ref = PropertyRef::Property(prop.name); build_node_filter_from_prop_condition(prop_ref, &prop.where_) } NodeFilter::Metadata(prop) => { + if prop.window.is_some() { + return Err(GraphError::InvalidGqlFilter( + "window is only valid for TemporalProperty".into(), + )); + } let prop_ref = PropertyRef::Metadata(prop.name); build_node_filter_from_prop_condition(prop_ref, &prop.where_) } NodeFilter::TemporalProperty(prop) => { let prop_ref = PropertyRef::TemporalProperty(prop.name); - build_node_filter_from_prop_condition(prop_ref, &prop.where_) + let mut pf = build_property_filter_from_condition::< + raphtory::db::graph::views::filter::model::node_filter::NodeFilter, + >(prop_ref, &prop.where_)?; + + if let Some(w) = prop.window { + if w.start > w.end { + return Err(GraphError::InvalidGqlFilter( + "window.start must be <= window.end".into(), + )); + } + pf = pf.with_window(TimeIndexEntry::start(w.start), TimeIndexEntry::end(w.end)); + } + Ok(CompositeNodeFilter::Property(pf)) } NodeFilter::And(and_filters) => { let mut iter = and_filters.into_iter().map(TryInto::try_into); @@ -1034,16 +1061,37 @@ impl TryFrom for CompositeEdgeFilter { })) } EdgeFilter::Property(p) => { + if p.window.is_some() { + return Err(GraphError::InvalidGqlFilter( + "window is only valid for TemporalProperty".into(), + )); + } let prop_ref = PropertyRef::Property(p.name); build_edge_filter_from_prop_condition(prop_ref, &p.where_) } EdgeFilter::Metadata(p) => { + if p.window.is_some() { + return Err(GraphError::InvalidGqlFilter( + "window is only valid for TemporalProperty".into(), + )); + } let prop_ref = PropertyRef::Metadata(p.name); build_edge_filter_from_prop_condition(prop_ref, &p.where_) } EdgeFilter::TemporalProperty(p) => { let prop_ref = PropertyRef::TemporalProperty(p.name); - build_edge_filter_from_prop_condition(prop_ref, &p.where_) + let mut pf = build_property_filter_from_condition::< + raphtory::db::graph::views::filter::model::edge_filter::EdgeFilter, + >(prop_ref, &p.where_)?; + if let Some(w) = p.window { + if w.start > w.end { + return Err(GraphError::InvalidGqlFilter( + "window.start must be <= window.end".into(), + )); + } + pf = pf.with_window(TimeIndexEntry::start(w.start), TimeIndexEntry::end(w.end)); + } + Ok(CompositeEdgeFilter::Property(pf)) } EdgeFilter::And(and_filters) => { let mut iter = and_filters.into_iter().map(TryInto::try_into); From 3251d27d21b1d3a0761d416aa3a831668b8baea6 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:18:20 +0000 Subject: [PATCH 14/42] ref --- raphtory/src/db/graph/views/filter/mod.rs | 106 +++++++++++++++++- .../graph/views/filter/model/edge_filter.rs | 2 +- .../views/filter/model/property_filter.rs | 13 +-- 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index e16fc78e97..4466cb5014 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -3822,6 +3822,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter, + &expected_results, + TestVariants::All, + ); // Wider window includes node 3 let filter = NodeFilter::window(1, 5) @@ -3838,6 +3845,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter, + &expected_results, + TestVariants::All, + ); } #[test] @@ -3856,6 +3870,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter1.clone(), + &expected_results, + TestVariants::All, + ); assert_filter_nodes_results( init_nodes_graph, IdentityGraphTransformer, @@ -3863,6 +3884,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter2.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -3881,6 +3909,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); let filter = NodeFilter::window(3, 5) .property("p20") @@ -3896,6 +3931,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -3914,11 +3956,20 @@ pub(crate) mod test_filters { .sum() .eq(6u64); + let filter = filter1.and(filter2); + let expected_results = vec!["3"]; assert_filter_nodes_results( init_nodes_graph, IdentityGraphTransformer, - filter1.and(filter2).clone(), + filter.clone(), + &expected_results, + TestVariants::All, + ); + assert_search_nodes_results( + init_nodes_graph, + IdentityGraphTransformer, + filter, &expected_results, TestVariants::All, ); @@ -9948,6 +9999,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); let filter = EdgeFilter::window(1, 5) .property("p2") @@ -9970,6 +10028,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -9987,6 +10052,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter1.clone(), + &expected_results, + TestVariants::All, + ); assert_filter_edges_results( init_edges_graph, IdentityGraphTransformer, @@ -9994,6 +10066,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter2.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -10012,6 +10091,13 @@ pub(crate) mod test_filters { &expected_any, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter_any.clone(), + &expected_any, + TestVariants::All, + ); let filter_all = EdgeFilter::window(2, 4) .property("p20") @@ -10027,6 +10113,13 @@ pub(crate) mod test_filters { &expected_all, TestVariants::All, ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter_all.clone(), + &expected_all, + TestVariants::All, + ); } #[test] @@ -10043,11 +10136,20 @@ pub(crate) mod test_filters { .sum() .eq(6u64); + let filter = filter1.and(filter2); + let expected_results = vec!["2->1"]; assert_filter_edges_results( init_edges_graph, IdentityGraphTransformer, - filter1.and(filter2).clone(), + filter.clone(), + &expected_results, + TestVariants::All, + ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter, &expected_results, TestVariants::All, ); 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 454b7bd60f..cac101c0a6 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -12,7 +12,7 @@ use crate::{ errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::{entities::GID, storage::timeindex::TimeIndexEntry}; +use raphtory_api::core::entities::GID; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; 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 c29479c0f0..03b215e662 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -3,7 +3,7 @@ use crate::{ api::{ properties::{internal::InternalPropertiesOps, Metadata, Properties}, view::{ - internal::{GraphTimeSemanticsOps, GraphView, NodeTimeSemanticsOps}, + internal::{GraphView, NodeTimeSemanticsOps}, node::NodeViewOps, EdgeViewOps, }, @@ -23,7 +23,7 @@ use crate::{ }, }, errors::GraphError, - prelude::{GraphViewOps, PropertiesOps, TimeOps}, + prelude::{GraphViewOps, PropertiesOps}, }; use itertools::Itertools; use raphtory_api::core::{ @@ -39,12 +39,9 @@ use raphtory_api::core::{ timeindex::{AsTime, TimeIndexEntry}, }, }; -use raphtory_storage::{ - core_ops::CoreGraphOps, - graph::{ - edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, - nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, - }, +use raphtory_storage::graph::{ + edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, }; use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Deref, sync::Arc}; From ff7ff9d6aecd2ddc8b73cfd7090d3686a2360aaa Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:09:51 +0000 Subject: [PATCH 15/42] impl edge node filtering, add few tests --- .../views/filter/edge_field_filtered_graph.rs | 90 --- .../views/filter/edge_node_filtered_graph.rs | 92 +++ .../filter/edge_property_filtered_graph.rs | 4 +- .../filter/exploded_edge_property_filter.rs | 24 +- raphtory/src/db/graph/views/filter/mod.rs | 466 +++++++----- .../db/graph/views/filter/model/and_filter.rs | 5 +- .../graph/views/filter/model/edge_filter.rs | 696 +++++++++++++----- .../filter/model/exploded_edge_filter.rs | 116 +++ .../src/db/graph/views/filter/model/mod.rs | 6 +- .../graph/views/filter/model/node_filter.rs | 63 +- .../db/graph/views/filter/model/not_filter.rs | 5 +- .../db/graph/views/filter/model/or_filter.rs | 5 +- .../views/filter/model/property_filter.rs | 8 +- raphtory/src/db/graph/views/window_graph.rs | 3 +- raphtory/src/search/edge_filter_executor.rs | 2 +- raphtory/src/search/searcher.rs | 3 +- 16 files changed, 1057 insertions(+), 531 deletions(-) delete mode 100644 raphtory/src/db/graph/views/filter/edge_field_filtered_graph.rs create mode 100644 raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs create mode 100644 raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs 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..1c090d6095 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -0,0 +1,92 @@ +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::model::{edge_filter::Endpoint, node_filter::CompositeNodeFilter}, + }, + 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: CompositeNodeFilter, +} + +impl EdgeNodeFilteredGraph { + #[inline] + pub fn new(graph: G, endpoint: Endpoint, node_cf: CompositeNodeFilter) -> Self { + Self { + graph, + endpoint, + filter: node_cf, + } + } +} + +impl Base for EdgeNodeFilteredGraph { + type Base = G; + #[inline] + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for EdgeNodeFilteredGraph {} +impl Immutable for EdgeNodeFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritListOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritNodeFilterOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritEdgeLayerFilterOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritExplodedEdgeFilterOps for EdgeNodeFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>> 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; + } + + // Fetch the endpoint node and delegate to the node composite filter. + let ok = match self.endpoint { + Endpoint::Src => self + .filter + .matches_node(&self.graph, self.graph.core_node(edge.src()).as_ref()), + Endpoint::Dst => self + .filter + .matches_node(&self.graph, self.graph.core_node(edge.dst()).as_ref()), + }; + ok + } +} 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 429ac56023..bfc46c464f 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 @@ -107,14 +107,12 @@ mod test_edge_property_filtered_graph { views::{ deletion_graph::PersistentGraph, filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, + edge_filter::EdgeFilter, property_filter::PropertyFilterOps, ComposableFilter, PropertyFilterFactory, }, }, }, }, - errors::GraphError, prelude::*, test_utils::{ build_edge_deletions, build_edge_list, build_graph_from_edge_list, build_window, 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 3bcd1b2f51..a2c36ce83f 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 @@ -10,9 +10,8 @@ use crate::{ InheritTimeSemantics, InternalExplodedEdgeFilterOps, Static, }, }, - graph::views::filter::{internal::CreateFilter, model::edge_filter::ExplodedEdgeFilter}, + graph::views::filter::model::exploded_edge_filter::ExplodedEdgeFilter, }, - errors::GraphError, prelude::{GraphViewOps, LayerOps, PropertyFilter}, }; use raphtory_api::{ @@ -53,22 +52,6 @@ impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilteredGraph { } } -impl CreateFilter for PropertyFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = ExplodedEdgePropertyFilteredGraph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - let prop_id = self.resolve_prop_id(graph.edge_meta(), graph.num_layers() > 1)?; - Ok(ExplodedEdgePropertyFilteredGraph::new( - graph.clone(), - prop_id, - self, - )) - } -} - impl Base for ExplodedEdgePropertyFilteredGraph { type Base = G; @@ -158,8 +141,9 @@ mod test_exploded_edge_property_filtered_graph { exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, internal::CreateFilter, 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/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 4466cb5014..3ca6538866 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1,11 +1,10 @@ use crate::db::graph::views::filter::model::{ - edge_filter::EdgeFieldFilter, node_filter::{NodeNameFilter, NodeTypeFilter}, property_filter::PropertyFilter, }; 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_property_filter; pub(crate) mod internal; @@ -20,7 +19,7 @@ pub mod or_filtered_graph; #[cfg(test)] mod test_fluent_builder_apis { use crate::db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, EdgeFilter, EdgeFilterOps}, + edge_filter::EdgeFilter, node_filter::{CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps}, property_filter::{ElemQualifierOps, Op, PropertyFilter, PropertyFilterOps, PropertyRef}, ComposableFilter, Filter, PropertyFilterFactory, TryAsCompositeFilter, @@ -154,21 +153,21 @@ mod test_fluent_builder_apis { assert_eq!(node_composite_filter, node_composite_filter2); } - #[test] - fn test_edge_src_filter_build() { - let filter_expr = EdgeFilter::src().name().eq("raphtory"); - let edge_property_filter = filter_expr.try_as_composite_edge_filter().unwrap(); - let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory")); - assert_eq!(edge_property_filter, edge_property_filter2); - } - - #[test] - fn test_edge_dst_filter_build() { - let filter_expr = EdgeFilter::dst().name().eq("raphtory"); - let edge_property_filter = filter_expr.try_as_composite_edge_filter().unwrap(); - let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("dst", "raphtory")); - assert_eq!(edge_property_filter, edge_property_filter2); - } + // #[test] + // fn test_edge_src_filter_build() { + // let filter_expr = EdgeFilter::src().name().eq("raphtory"); + // let edge_property_filter = filter_expr.try_as_composite_edge_filter().unwrap(); + // let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory")); + // assert_eq!(edge_property_filter, edge_property_filter2); + // } + // + // #[test] + // fn test_edge_dst_filter_build() { + // let filter_expr = EdgeFilter::dst().name().eq("raphtory"); + // let edge_property_filter = filter_expr.try_as_composite_edge_filter().unwrap(); + // let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("dst", "raphtory")); + // assert_eq!(edge_property_filter, edge_property_filter2); + // } #[test] fn test_edge_filter_composition() { @@ -189,42 +188,42 @@ mod test_fluent_builder_apis { .try_as_composite_edge_filter() .unwrap(); - let edge_composite_filter2 = CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "fire_nation"))), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Metadata("p2".into()), - 2u64, - ))), - )), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p1".into()), - 1u64, - ))), - )), - Box::new(CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::Property( - PropertyFilter::eq(PropertyRef::TemporalProperty("p3".into()), 5u64) - .with_op(Op::Any), - )), - Box::new(CompositeEdgeFilter::Property( - PropertyFilter::eq(PropertyRef::TemporalProperty("p4".into()), 7u64) - .with_op(Op::Last), - )), - )), - )), - Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory"))), - )), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p5".into()), - 9u64, - ))), - ); - - assert_eq!(edge_composite_filter, edge_composite_filter2); + // let edge_composite_filter2 = CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "fire_nation"))), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Metadata("p2".into()), + // 2u64, + // ))), + // )), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Property("p1".into()), + // 1u64, + // ))), + // )), + // Box::new(CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::Property( + // PropertyFilter::eq(PropertyRef::TemporalProperty("p3".into()), 5u64) + // .with_op(Op::Any), + // )), + // Box::new(CompositeEdgeFilter::Property( + // PropertyFilter::eq(PropertyRef::TemporalProperty("p4".into()), 7u64) + // .with_op(Op::Last), + // )), + // )), + // )), + // Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory"))), + // )), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Property("p5".into()), + // 9u64, + // ))), + // ); + // + // assert_eq!(edge_composite_filter, edge_composite_filter2); } } @@ -320,63 +319,63 @@ mod test_composite_filters { .to_string() ); - assert_eq!( - "((((edge_type IS_NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IS_IN [2, 10])) OR (src == pometry OR p5 == 9))", - CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::And( - Box::new(CompositeEdgeFilter::Edge(Filter::is_not_in( - "edge_type", - vec!["fire_nation".into(), "water_tribe".into()], - ))), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p2".to_string()), - 2u64, - ))), - )), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p1".to_string()), - 1u64, - ))), - )), - Box::new(CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::Property(PropertyFilter::le( - PropertyRef::Property("p3".to_string()), - 5u64, - ))), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::is_in( - PropertyRef::Property("p4".to_string()), - vec![Prop::U64(10), Prop::U64(2)], - ))), - )), - )), - Box::new(CompositeEdgeFilter::Or( - Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "pometry"))), - Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p5".to_string()), - 9u64, - ))), - )), - ) - .to_string() - ); - - assert_eq!( - "(name FUZZY_SEARCH(1,true) shivam AND nation FUZZY_SEARCH(1,false) air_nomad)", - CompositeEdgeFilter::And( - Box::from(CompositeEdgeFilter::Edge(Filter::fuzzy_search( - "name", "shivam", 1, true - ))), - Box::from(CompositeEdgeFilter::Property(PropertyFilter::fuzzy_search( - PropertyRef::Property("nation".to_string()), - "air_nomad", - 1, - false, - ))), - ) - .to_string() - ); + // assert_eq!( + // "((((edge_type IS_NOT_IN [fire_nation, water_tribe] AND p2 == 2) AND p1 == 1) AND (p3 <= 5 OR p4 IS_IN [2, 10])) OR (src == pometry OR p5 == 9))", + // CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::And( + // Box::new(CompositeEdgeFilter::Edge(Filter::is_not_in( + // "edge_type", + // vec!["fire_nation".into(), "water_tribe".into()], + // ))), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Property("p2".to_string()), + // 2u64, + // ))), + // )), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Property("p1".to_string()), + // 1u64, + // ))), + // )), + // Box::new(CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::le( + // PropertyRef::Property("p3".to_string()), + // 5u64, + // ))), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::is_in( + // PropertyRef::Property("p4".to_string()), + // vec![Prop::U64(10), Prop::U64(2)], + // ))), + // )), + // )), + // Box::new(CompositeEdgeFilter::Or( + // Box::new(CompositeEdgeFilter::Edge(Filter::eq("src", "pometry"))), + // Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + // PropertyRef::Property("p5".to_string()), + // 9u64, + // ))), + // )), + // ) + // .to_string() + // ); + + // assert_eq!( + // "(name FUZZY_SEARCH(1,true) shivam AND nation FUZZY_SEARCH(1,false) air_nomad)", + // CompositeEdgeFilter::And( + // Box::from(CompositeEdgeFilter::Edge(Filter::fuzzy_search( + // "name", "shivam", 1, true + // ))), + // Box::from(CompositeEdgeFilter::Property(PropertyFilter::fuzzy_search( + // PropertyRef::Property("nation".to_string()), + // "air_nomad", + // 1, + // false, + // ))), + // ) + // .to_string() + // ); } #[test] @@ -1497,6 +1496,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 } @@ -7739,16 +7767,83 @@ 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, ComposableFilter, + PropertyFilterFactory, }, 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"); @@ -10165,38 +10260,37 @@ pub(crate) mod test_filters { }, views::filter::{ model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, - AndFilter, ComposableFilter, PropertyFilterFactory, TryAsCompositeFilter, + edge_filter::EdgeFilter, property_filter::PropertyFilterOps, AndFilter, + ComposableFilter, PropertyFilterFactory, TryAsCompositeFilter, }, test_filters::{init_edges_graph, IdentityGraphTransformer}, - EdgeFieldFilter, + // 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() - .name() - .eq("3") - .and(EdgeFilter::dst().name().eq("1")); - let expected_results = vec!["3->1"]; - assert_filter_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::All, - ); - } + // #[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() + // .name() + // .eq("3") + // .and(EdgeFilter::dst().name().eq("1")); + // let expected_results = vec!["3->1"]; + // assert_filter_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::EventOnly, + // ); + // assert_search_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::All, + // ); + // } #[test] fn test_unique_results_from_composite_filters() { @@ -10372,21 +10466,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::NonDiskOnly, ); - let filter = filter.try_as_composite_edge_filter().unwrap(); - assert_filter_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - vec![TestGraphVariants::Graph], - ); - assert_search_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::NonDiskOnly, - ); + // let filter = filter.try_as_composite_edge_filter().unwrap(); + // assert_filter_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // vec![TestGraphVariants::Graph], + // ); + // assert_search_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::NonDiskOnly, + // ); let filter = EdgeFilter::property("p2") .eq(4u64) @@ -10441,21 +10535,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::NonDiskOnly, ); - let filter = filter.try_as_composite_edge_filter().unwrap(); - assert_filter_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - vec![TestGraphVariants::Graph], - ); - assert_search_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::NonDiskOnly, - ); + // let filter = filter.try_as_composite_edge_filter().unwrap(); + // assert_filter_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // vec![TestGraphVariants::Graph], + // ); + // assert_search_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::NonDiskOnly, + // ); let filter = EdgeFilter::dst() .name() @@ -10476,21 +10570,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - let filter = filter.try_as_composite_edge_filter().unwrap(); - assert_filter_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::EventOnly, - ); - assert_search_edges_results( - init_edges_graph, - IdentityGraphTransformer, - filter.clone(), - &expected_results, - TestVariants::All, - ); + // let filter = filter.try_as_composite_edge_filter().unwrap(); + // assert_filter_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::EventOnly, + // ); + // assert_search_edges_results( + // init_edges_graph, + // IdentityGraphTransformer, + // filter.clone(), + // &expected_results, + // TestVariants::All, + // ); let filter = EdgeFilter::src() .name() 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 cac101c0a6..4138c208b6 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -2,32 +2,45 @@ use crate::{ db::{ api::view::BoxableGraphView, graph::views::filter::{ + edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, model::{ - node_filter::CompositeNodeFilter, property_filter::PropertyFilter, AndFilter, - Filter, NotFilter, OrFilter, PropertyFilterFactory, TryAsCompositeFilter, Windowed, + exploded_edge_filter::CompositeExplodedEdgeFilter, + node_filter::{ + CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps, NodeIdFilter, + NodeIdFilterBuilder, NodeNameFilter, NodeNameFilterBuilder, NodeTypeFilter, + NodeTypeFilterBuilder, + }, + property_filter::{ + ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, + Op, OpChainBuilder, PropertyFilter, PropertyFilterBuilder, PropertyFilterOps, + WindowedPropertyRef, + }, + AndFilter, NotFilter, OrFilter, PropertyFilterFactory, TryAsCompositeFilter, + Windowed, }, }, }, errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::entities::GID; +use raphtory_api::core::{ + entities::{properties::prop::Prop, GID}, + storage::timeindex::TimeIndexEntry, +}; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; -#[derive(Debug, Clone)] -pub struct EdgeFieldFilter(pub Filter); - -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), And(Box, Box), Or(Box, Box), @@ -37,7 +50,8 @@ 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::And(left, right) => write!(f, "({} AND {})", left, right), CompositeEdgeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), @@ -54,7 +68,16 @@ impl CreateFilter for CompositeEdgeFilter { graph: G, ) -> Result, GraphError> { match self { - CompositeEdgeFilter::Edge(i) => Ok(Arc::new(EdgeFieldFilter(i).create_filter(graph)?)), + CompositeEdgeFilter::Src(node_cf) => Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Src, + node_cf, + ))), + CompositeEdgeFilter::Dst(node_cf) => Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Dst, + node_cf, + ))), CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeEdgeFilter::And(l, r) => Ok(Arc::new( AndFilter { @@ -82,11 +105,9 @@ 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 { @@ -94,321 +115,592 @@ impl TryAsCompositeFilter for CompositeEdgeFilter { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CompositeExplodedEdgeFilter { - Property(PropertyFilter), - And( - Box, - Box, - ), - Or( - Box, - Box, - ), - Not(Box), +// User facing entry for building edge filters. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub struct EdgeFilter; + +impl PropertyFilterFactory for EdgeFilter {} + +impl EdgeFilter { + #[inline] + pub fn src() -> EdgeEndpoint { + EdgeEndpoint::src() + } + + #[inline] + pub fn dst() -> EdgeEndpoint { + EdgeEndpoint::dst() + } + + #[inline] + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end) + } } -impl Display for CompositeExplodedEdgeFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CompositeExplodedEdgeFilter::Property(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), - } +// Endpoint selector that exposes **node** filter builders for src/dst. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub struct EdgeEndpoint(Endpoint); + +impl EdgeEndpoint { + #[inline] + pub fn src() -> Self { + Self(Endpoint::Src) + } + + #[inline] + pub fn dst() -> Self { + Self(Endpoint::Dst) } } -impl CreateFilter for CompositeExplodedEdgeFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; +impl EdgeEndpoint { + #[inline] + pub fn id(self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::id(), self.0) + } - fn create_filter<'graph, G: GraphViewOps<'graph>>( + #[inline] + pub fn name(self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::name(), self.0) + } + + #[inline] + pub fn node_type(self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::node_type(), self.0) + } + + #[inline] + pub fn property( self, - graph: G, - ) -> Result, GraphError> { - match self { - CompositeExplodedEdgeFilter::Property(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)?)) - } + name: impl Into, + ) -> EndpointWrapper> { + EndpointWrapper::new(PropertyFilterBuilder::::new(name), self.0) + } + + #[inline] + pub fn metadata( + self, + name: impl Into, + ) -> EndpointWrapper> { + EndpointWrapper::new(MetadataFilterBuilder::::new(name), self.0) + } + + #[inline] + pub fn window( + self, + start: S, + end: E, + ) -> EndpointWrapper> { + EndpointWrapper::new(NodeFilter::window(start, end), self.0) + } +} + +// 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 EndpointWrapper { + inner: T, + endpoint: Endpoint, +} + +impl EndpointWrapper { + #[inline] + pub fn new(inner: T, endpoint: Endpoint) -> Self { + Self { inner, endpoint } + } + + #[inline] + pub fn map(self, f: impl FnOnce(T) -> U) -> EndpointWrapper { + EndpointWrapper { + inner: f(self.inner), + endpoint: self.endpoint, + } + } + + #[inline] + pub fn with(&self, inner: U) -> EndpointWrapper { + EndpointWrapper { + inner, + endpoint: self.endpoint, } } } -impl TryAsCompositeFilter for CompositeExplodedEdgeFilter { +impl Display for EndpointWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl TryAsCompositeFilter for EndpointWrapper +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) + let filter = self.inner.try_as_composite_node_filter()?; + Ok(match self.endpoint { + Endpoint::Src => CompositeEdgeFilter::Src(filter), + Endpoint::Dst => CompositeEdgeFilter::Dst(filter), + }) } fn try_as_composite_exploded_edge_filter( &self, ) -> Result { - Ok(self.clone()) + Err(GraphError::NotSupported) } } -pub trait InternalEdgeFilterBuilderOps: Send + Sync { - fn field_name(&self) -> &'static str; +impl CreateFilter for EndpointWrapper +where + T: TryAsCompositeFilter + Clone, +{ + type EntityFiltered<'graph, G: GraphViewOps<'graph>> + = Arc + where + T: 'graph; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> + where + T: 'graph, + { + let filter = self.try_as_composite_edge_filter()?; + filter.create_filter(graph) + } } -impl InternalEdgeFilterBuilderOps for Arc { - fn field_name(&self) -> &'static str { - self.deref().field_name() +impl InternalPropertyFilterOps for EndpointWrapper { + type Marker = T::Marker; + #[inline] + fn property_ref(&self) -> crate::db::graph::views::filter::model::property_filter::PropertyRef { + self.inner.property_ref() + } + + #[inline] + fn ops(&self) -> &[Op] { + self.inner.ops() + } + + #[inline] + fn window(&self) -> Option<(TimeIndexEntry, TimeIndexEntry)> { + self.inner.window() } } -pub trait EdgeFilterOps { - fn eq(&self, value: impl Into) -> EdgeFieldFilter; +impl EndpointWrapper { + #[inline] + pub fn eq(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.eq(v)) + } - fn ne(&self, value: impl Into) -> EdgeFieldFilter; + #[inline] + pub fn ne(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.ne(v)) + } + + #[inline] + pub fn le(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.le(v)) + } + + #[inline] + pub fn ge(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.ge(v)) + } + + #[inline] + pub fn lt(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.lt(v)) + } - fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; + #[inline] + pub fn gt(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.gt(v)) + } - fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; + #[inline] + pub fn is_in( + self, + vals: impl IntoIterator, + ) -> EndpointWrapper> { + self.with(self.inner.is_in(vals)) + } - fn starts_with(&self, value: impl Into) -> EdgeFieldFilter; + #[inline] + pub fn is_not_in( + self, + vals: impl IntoIterator, + ) -> EndpointWrapper> { + self.with(self.inner.is_not_in(vals)) + } - fn ends_with(&self, value: impl Into) -> EdgeFieldFilter; + #[inline] + pub fn is_none(self) -> EndpointWrapper> { + self.with(self.inner.is_none()) + } - fn contains(&self, value: impl Into) -> EdgeFieldFilter; + #[inline] + pub fn is_some(self) -> EndpointWrapper> { + self.with(self.inner.is_some()) + } - fn not_contains(&self, value: impl Into) -> EdgeFieldFilter; + #[inline] + pub fn starts_with(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.starts_with(v)) + } - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> EdgeFieldFilter; + #[inline] + pub fn ends_with(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.ends_with(v)) + } + + #[inline] + pub fn contains(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.contains(v)) + } + + #[inline] + pub fn not_contains(self, v: impl Into) -> EndpointWrapper> { + self.with(self.inner.not_contains(v)) + } + + #[inline] + pub fn fuzzy_search( + self, + s: impl Into, + d: usize, + p: bool, + ) -> EndpointWrapper> { + self.with(self.inner.fuzzy_search(s, d, p)) + } } -impl EdgeFilterOps for T { - fn eq(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::eq(self.field_name(), value)) +impl EndpointWrapper> { + #[inline] + pub fn any(self) -> Self { + self.map(|b| b.any()) } - fn ne(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ne(self.field_name(), value)) + #[inline] + pub fn all(self) -> Self { + self.map(|b| b.all()) } - fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::is_in(self.field_name(), values)) + #[inline] + pub fn len(self) -> Self { + self.map(|b| b.len()) } - fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::is_not_in(self.field_name(), values)) + #[inline] + pub fn sum(self) -> Self { + self.map(|b| b.sum()) } - fn starts_with(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::starts_with(self.field_name(), value.into())) + #[inline] + pub fn avg(self) -> Self { + self.map(|b| b.avg()) } - fn ends_with(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ends_with(self.field_name(), value.into())) + #[inline] + pub fn min(self) -> Self { + self.map(|b| b.min()) } - fn contains(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::contains(self.field_name(), value.into())) + #[inline] + pub fn max(self) -> Self { + self.map(|b| b.max()) } - fn not_contains(&self, value: impl Into) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::not_contains(self.field_name(), value.into())) + #[inline] + pub fn first(self) -> Self { + self.map(|b| b.first()) } - 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 last(self) -> Self { + self.map(|b| b.last()) } } -#[derive(Debug, Clone)] -pub struct EdgeIdFilterBuilder { - field: &'static str, +impl EndpointWrapper> { + #[inline] + pub fn property( + self, + name: impl Into, + ) -> EndpointWrapper> { + self.with(self.inner.property(name)) + } + + #[inline] + pub fn metadata( + self, + name: impl Into, + ) -> EndpointWrapper> { + self.with(self.inner.metadata(name)) + } } -impl EdgeIdFilterBuilder { +impl EndpointWrapper> { + #[inline] + pub fn temporal(self) -> Self { + self.map(|w| w.temporal()) + } + #[inline] - fn field_name(&self) -> &'static str { - self.field + pub fn any(self) -> Self { + self.map(|w| w.any()) } - pub fn eq>(&self, v: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::eq_id(self.field_name(), v)) + #[inline] + pub fn all(self) -> Self { + self.map(|w| w.all()) } - pub fn ne>(&self, v: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ne_id(self.field_name(), v)) + #[inline] + pub fn len(self) -> Self { + self.map(|w| w.len()) } - pub fn is_in(&self, vals: I) -> EdgeFieldFilter + #[inline] + pub fn sum(self) -> Self { + self.map(|w| w.sum()) + } + + #[inline] + pub fn avg(self) -> Self { + self.map(|w| w.avg()) + } + + #[inline] + pub fn min(self) -> Self { + self.map(|w| w.min()) + } + + #[inline] + pub fn max(self) -> Self { + self.map(|w| w.max()) + } + + #[inline] + pub fn first(self) -> Self { + self.map(|w| w.first()) + } + + #[inline] + pub fn last(self) -> Self { + self.map(|w| w.last()) + } +} + +impl EndpointWrapper { + #[inline] + pub fn eq>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.eq(v)) + } + + #[inline] + pub fn ne>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.ne(v)) + } + + #[inline] + pub fn is_in(&self, vals: I) -> EndpointWrapper where I: IntoIterator, V: Into, { - EdgeFieldFilter(Filter::is_in_id(self.field_name(), vals)) + self.with(self.inner.is_in(vals)) } - pub fn is_not_in(&self, vals: I) -> EdgeFieldFilter + #[inline] + pub fn is_not_in(&self, vals: I) -> EndpointWrapper where I: IntoIterator, V: Into, { - EdgeFieldFilter(Filter::is_not_in_id(self.field_name(), vals)) + self.with(self.inner.is_not_in(vals)) } - pub fn lt>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::lt(self.field_name(), value)) + #[inline] + pub fn lt>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.lt(v)) } - pub fn le>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::le(self.field_name(), value).into()) + #[inline] + pub fn le>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.le(v)) } - pub fn gt>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::gt(self.field_name(), value)) + #[inline] + pub fn gt>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.gt(v)) } - pub fn ge>(&self, value: V) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ge(self.field_name(), value)) + #[inline] + pub fn ge>(&self, v: V) -> EndpointWrapper { + self.with(self.inner.ge(v)) } - pub fn starts_with>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::starts_with(self.field_name(), s.into())) + // string-y id ops (if allowed by your NodeIdFilter validation) + #[inline] + pub fn starts_with>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.starts_with(s)) } - pub fn ends_with>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::ends_with(self.field_name(), s.into())) + #[inline] + pub fn ends_with>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.ends_with(s)) } - pub fn contains>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::contains(self.field_name(), s.into())) + #[inline] + pub fn contains>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.contains(s)) } - pub fn not_contains>(&self, s: S) -> EdgeFieldFilter { - EdgeFieldFilter(Filter::not_contains(self.field_name(), s.into())) + #[inline] + pub fn not_contains>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.not_contains(s)) } + #[inline] 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(), - ) + d: usize, + p: bool, + ) -> EndpointWrapper { + self.with(self.inner.fuzzy_search(s, d, p)) } } -pub struct EdgeSourceFilterBuilder; +impl EndpointWrapper { + #[inline] + pub fn eq>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.eq(s.into())) + } -impl InternalEdgeFilterBuilderOps for EdgeSourceFilterBuilder { - fn field_name(&self) -> &'static str { - "src" + #[inline] + pub fn ne>(&self, s: S) -> EndpointWrapper { + self.with(self.inner.ne(s.into())) } -} -pub struct EdgeDestinationFilterBuilder; + #[inline] + pub fn is_in(&self, vals: I) -> EndpointWrapper + where + I: IntoIterator, + { + self.with(self.inner.is_in(vals)) + } -impl InternalEdgeFilterBuilderOps for EdgeDestinationFilterBuilder { - fn field_name(&self) -> &'static str { - "dst" + #[inline] + pub fn is_not_in(&self, vals: I) -> EndpointWrapper + where + I: IntoIterator, + { + self.with(self.inner.is_not_in(vals)) } -} -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub struct EdgeFilter; + #[inline] + pub fn starts_with>(self, s: S) -> EndpointWrapper { + self.with(self.inner.starts_with(s.into())) + } -#[derive(Clone)] -pub enum EdgeEndpointFilter { - Src, - Dst, -} + #[inline] + pub fn ends_with>(self, s: S) -> EndpointWrapper { + self.with(self.inner.ends_with(s.into())) + } -impl EdgeEndpointFilter { - pub fn id(&self) -> EdgeIdFilterBuilder { - let field = match self { - EdgeEndpointFilter::Src => "src", - EdgeEndpointFilter::Dst => "dst", - }; - EdgeIdFilterBuilder { field } + #[inline] + pub fn contains>(self, s: S) -> EndpointWrapper { + self.with(self.inner.contains(s.into())) } - pub fn name(&self) -> Arc { - match self { - EdgeEndpointFilter::Src => Arc::new(EdgeSourceFilterBuilder), - EdgeEndpointFilter::Dst => Arc::new(EdgeDestinationFilterBuilder), - } + #[inline] + pub fn not_contains>(self, s: S) -> EndpointWrapper { + self.with(self.inner.not_contains(s.into())) + } + + #[inline] + pub fn fuzzy_search>( + self, + s: S, + d: usize, + p: bool, + ) -> EndpointWrapper { + self.with(self.inner.fuzzy_search(s.into(), d, p)) } } -impl EdgeFilter { - pub fn src() -> EdgeEndpointFilter { - EdgeEndpointFilter::Src +impl EndpointWrapper { + #[inline] + pub fn eq>(self, s: S) -> EndpointWrapper { + self.with(self.inner.eq(s.into())) } - pub fn dst() -> EdgeEndpointFilter { - EdgeEndpointFilter::Dst + #[inline] + pub fn ne>(self, s: S) -> EndpointWrapper { + self.with(self.inner.ne(s.into())) } - pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + #[inline] + pub fn is_in(self, vals: I) -> EndpointWrapper + where + I: IntoIterator, + { + self.with(self.inner.is_in(vals)) } -} -impl PropertyFilterFactory for EdgeFilter {} + #[inline] + pub fn is_not_in(self, vals: I) -> EndpointWrapper + where + I: IntoIterator, + { + self.with(self.inner.is_not_in(vals)) + } -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub struct ExplodedEdgeFilter; + #[inline] + pub fn starts_with>(self, s: S) -> EndpointWrapper { + self.with(self.inner.starts_with(s.into())) + } -impl PropertyFilterFactory for ExplodedEdgeFilter {} + #[inline] + pub fn ends_with>(self, s: S) -> EndpointWrapper { + self.with(self.inner.ends_with(s.into())) + } -impl ExplodedEdgeFilter { - pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + #[inline] + pub fn contains>(self, s: S) -> EndpointWrapper { + self.with(self.inner.contains(s.into())) } -} -impl TryAsCompositeFilter for EdgeFieldFilter { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) + #[inline] + pub fn not_contains>(self, s: S) -> EndpointWrapper { + self.with(self.inner.not_contains(s.into())) } - fn try_as_composite_edge_filter(&self) -> Result { - Ok(CompositeEdgeFilter::Edge(self.0.clone())) + #[inline] + pub fn fuzzy_search>( + self, + s: S, + d: usize, + p: bool, + ) -> EndpointWrapper { + self.with(self.inner.fuzzy_search(s.into(), d, p)) } +} - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) +impl EndpointWrapper> { + #[inline] + pub fn temporal(self) -> EndpointWrapper> { + self.clone().with(self.inner.temporal()) } } 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..31ac010068 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -0,0 +1,116 @@ +use crate::{ + db::{ + api::view::BoxableGraphView, + graph::views::filter::{ + exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, + internal::CreateFilter, + model::{ + edge_filter::CompositeEdgeFilter, node_filter::CompositeNodeFilter, + property_filter::PropertyFilter, AndFilter, NotFilter, OrFilter, + PropertyFilterFactory, TryAsCompositeFilter, Windowed, + }, + }, + }, + errors::GraphError, + prelude::{GraphViewOps, LayerOps}, +}; +use raphtory_core::utils::time::IntoTime; +use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompositeExplodedEdgeFilter { + Property(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::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 PropertyFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = ExplodedEdgePropertyFilteredGraph; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let prop_id = self.resolve_prop_id(graph.edge_meta(), graph.num_layers() > 1)?; + Ok(ExplodedEdgePropertyFilteredGraph::new( + graph.clone(), + prop_id, + self, + )) + } +} + +impl CreateFilter for CompositeExplodedEdgeFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + + fn create_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + match self { + CompositeExplodedEdgeFilter::Property(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)?)) + } + } + } +} + +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(Clone, Debug, Copy, PartialEq, Eq)] +pub struct ExplodedEdgeFilter; + +impl PropertyFilterFactory for ExplodedEdgeFilter {} + +impl ExplodedEdgeFilter { + #[inline] + pub fn window(start: S, end: E) -> Windowed { + Windowed::from_times(start, end) + } +} diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 1e904b929d..498b520b78 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -1,7 +1,8 @@ pub(crate) use crate::db::graph::views::filter::model::and_filter::AndFilter; use crate::{ db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter, EdgeFieldFilter}, + edge_filter::{CompositeEdgeFilter, EndpointWrapper}, + exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, node_filter::{CompositeNodeFilter, NodeNameFilter, NodeTypeFilter}, not_filter::NotFilter, @@ -24,6 +25,7 @@ use std::{collections::HashSet, fmt, fmt::Display, marker::PhantomData, ops::Der 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; @@ -382,7 +384,7 @@ 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 {} 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 2e593844e8..1665b9b91f 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,22 +1,29 @@ use crate::{ db::{ api::view::BoxableGraphView, - graph::views::filter::{ - internal::CreateFilter, - model::{ - edge_filter::{CompositeEdgeFilter, CompositeExplodedEdgeFilter}, - filter_operator::FilterOperator, - property_filter::PropertyFilter, - AndFilter, Filter, FilterValue, NotFilter, OrFilter, PropertyFilterFactory, - TryAsCompositeFilter, Windowed, + graph::{ + node::NodeView, + views::filter::{ + internal::CreateFilter, + model::{ + edge_filter::CompositeEdgeFilter, + exploded_edge_filter::CompositeExplodedEdgeFilter, + filter_operator::FilterOperator, property_filter::PropertyFilter, AndFilter, + Filter, FilterValue, NotFilter, OrFilter, PropertyFilterFactory, + TryAsCompositeFilter, Windowed, + }, }, }, }, errors::GraphError, - prelude::GraphViewOps, + prelude::{GraphViewOps, NodeViewOps}, }; use raphtory_api::core::entities::{GidType, GID}; use raphtory_core::utils::time::IntoTime; +use raphtory_storage::{ + core_ops::CoreGraphOps, + graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, +}; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; #[derive(Debug, Clone)] @@ -85,6 +92,41 @@ impl Display for CompositeNodeFilter { } } +impl CompositeNodeFilter { + pub fn matches_node<'graph, G: GraphViewOps<'graph>>( + &self, + graph: &G, + node: NodeStorageRef, + ) -> bool { + match self { + CompositeNodeFilter::Node(filter) => { + let view = NodeView::new_internal(graph, node.vid()); + match filter.field_name.as_str() { + "node_id" => filter.id_matches(view.id().as_ref()), + "node_name" => filter.matches(Some(&view.name())), + "node_type" => filter.matches(view.node_type().as_deref()), + _ => false, + } + } + CompositeNodeFilter::Property(filter) => { + let meta = graph.node_meta(); + let expect_map = false; + let Ok(prop_id) = filter.resolve_prop_id(&meta, expect_map) else { + return false; + }; + filter.matches_node(graph, prop_id, node) + } + CompositeNodeFilter::And(l, r) => { + l.matches_node(graph, node) && r.matches_node(graph, node) + } + CompositeNodeFilter::Or(l, r) => { + l.matches_node(graph, node) || r.matches_node(graph, node) + } + CompositeNodeFilter::Not(x) => !x.matches_node(graph, node), + } + } +} + impl CreateFilter for CompositeNodeFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; @@ -199,6 +241,7 @@ pub trait NodeFilterBuilderOps: InternalNodeFilterBuilderOps { impl NodeFilterBuilderOps for T {} +#[derive(Clone, Debug)] pub struct NodeIdFilterBuilder; impl NodeIdFilterBuilder { @@ -273,6 +316,7 @@ impl NodeIdFilterBuilder { } } +#[derive(Clone, Debug)] pub struct NodeNameFilterBuilder; impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { @@ -283,6 +327,7 @@ impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { } } +#[derive(Clone, Debug)] pub struct NodeTypeFilterBuilder; impl InternalNodeFilterBuilderOps for NodeTypeFilterBuilder { 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 03b215e662..8ed3285142 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -12,10 +12,8 @@ use crate::{ edge::EdgeView, node::NodeView, views::filter::model::{ - edge_filter::{ - CompositeEdgeFilter, CompositeExplodedEdgeFilter, EdgeFilter, - ExplodedEdgeFilter, - }, + edge_filter::{CompositeEdgeFilter, EdgeFilter}, + exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, filter_operator::FilterOperator, node_filter::{CompositeNodeFilter, NodeFilter}, TryAsCompositeFilter, @@ -1757,7 +1755,7 @@ impl PropertyFilterOps for T { } #[derive(Clone)] -pub struct PropertyFilterBuilder(pub String, PhantomData); +pub struct PropertyFilterBuilder(String, PhantomData); impl PropertyFilterBuilder { pub fn new(prop: impl Into) -> Self { diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 3f7c083687..25b0cef4fa 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -3285,8 +3285,7 @@ mod views_test { TestGraphVariants, TestVariants, WindowGraphTransformer, }, views::filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, + edge_filter::EdgeFilter, property_filter::PropertyFilterOps, ComposableFilter, PropertyFilterFactory, }, }, diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 12a53216e5..5c6c449864 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -6,7 +6,7 @@ use crate::{ views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeEdgeFilter, EdgeFieldFilter, EdgeFilter}, + edge_filter::{CompositeEdgeFilter, EdgeFilter}, property_filter::PropertyRef, Filter, }, diff --git a/raphtory/src/search/searcher.rs b/raphtory/src/search/searcher.rs index b1ffc3e527..df5e657c32 100644 --- a/raphtory/src/search/searcher.rs +++ b/raphtory/src/search/searcher.rs @@ -182,8 +182,7 @@ mod search_tests { db::{ api::view::SearchableGraphOps, graph::views::filter::model::{ - edge_filter::{EdgeFilter, EdgeFilterOps}, - property_filter::PropertyFilterOps, + edge_filter::EdgeFilter, property_filter::PropertyFilterOps, PropertyFilterFactory, TryAsCompositeFilter, }, }, From 95a45495586ad8866b04efafa139af51165ab528 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:07:25 +0000 Subject: [PATCH 16/42] rid redundant code --- .../views/filter/edge_node_filtered_graph.rs | 97 +++++++++++++------ .../graph/views/filter/model/edge_filter.rs | 26 +++-- .../graph/views/filter/model/node_filter.rs | 59 ++--------- 3 files changed, 91 insertions(+), 91 deletions(-) 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 index 1c090d6095..99d2152e2f 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -20,24 +20,24 @@ use raphtory_storage::{ }; #[derive(Debug, Clone)] -pub struct EdgeNodeFilteredGraph { +pub struct EdgeNodeFilteredGraph { graph: G, endpoint: Endpoint, - filter: CompositeNodeFilter, + filtered_graph: F, } -impl EdgeNodeFilteredGraph { +impl EdgeNodeFilteredGraph { #[inline] - pub fn new(graph: G, endpoint: Endpoint, node_cf: CompositeNodeFilter) -> Self { + pub fn new(graph: G, endpoint: Endpoint, filtered_graph: F) -> Self { Self { graph, endpoint, - filter: node_cf, + filtered_graph, } } } -impl Base for EdgeNodeFilteredGraph { +impl Base for EdgeNodeFilteredGraph { type Base = G; #[inline] fn base(&self) -> &Self::Base { @@ -45,23 +45,61 @@ impl Base for EdgeNodeFilteredGraph { } } -impl Static for EdgeNodeFilteredGraph {} -impl Immutable for EdgeNodeFilteredGraph {} +impl Static for EdgeNodeFilteredGraph {} +impl Immutable for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritCoreGraphOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeFilterOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeLayerFilterOps for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritExplodedEdgeFilterOps for EdgeNodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritCoreGraphOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritStorageOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritLayerOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritListOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritMaterialize + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritNodeFilterOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritPropertiesOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritTimeSemantics + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritNodeHistoryFilter + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritEdgeHistoryFilter + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritEdgeLayerFilterOps + for EdgeNodeFilteredGraph +{ +} +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritExplodedEdgeFilterOps + for EdgeNodeFilteredGraph +{ +} -impl<'graph, G: GraphViewOps<'graph>> InternalEdgeFilterOps for EdgeNodeFilteredGraph { +impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InternalEdgeFilterOps + for EdgeNodeFilteredGraph +{ #[inline] fn internal_edge_filtered(&self) -> bool { true @@ -78,15 +116,14 @@ impl<'graph, G: GraphViewOps<'graph>> InternalEdgeFilterOps for EdgeNodeFiltered return false; } - // Fetch the endpoint node and delegate to the node composite filter. - let ok = match self.endpoint { - Endpoint::Src => self - .filter - .matches_node(&self.graph, self.graph.core_node(edge.src()).as_ref()), - Endpoint::Dst => self - .filter - .matches_node(&self.graph, self.graph.core_node(edge.dst()).as_ref()), + let src_binding = self.graph.core_node(edge.src()); + let dst_binding = self.graph.core_node(edge.dst()); + let node_ref = match self.endpoint { + Endpoint::Src => src_binding.as_ref(), + Endpoint::Dst => dst_binding.as_ref(), }; - ok + + self.filtered_graph + .internal_filter_node(node_ref, layer_ids) } } 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 4138c208b6..1dc8421730 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -68,16 +68,22 @@ impl CreateFilter for CompositeEdgeFilter { graph: G, ) -> Result, GraphError> { match self { - CompositeEdgeFilter::Src(node_cf) => Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::Src, - node_cf, - ))), - CompositeEdgeFilter::Dst(node_cf) => Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::Dst, - node_cf, - ))), + CompositeEdgeFilter::Src(filter) => { + let filtered_graph = filter.clone().create_filter(graph.clone())?; + Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Src, + filtered_graph, + ))) + } + CompositeEdgeFilter::Dst(filter) => { + let filtered_graph = filter.clone().create_filter(graph.clone())?; + Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Dst, + filtered_graph, + ))) + } CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeEdgeFilter::And(l, r) => Ok(Arc::new( AndFilter { 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 1665b9b91f..d13dd50e75 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,29 +1,21 @@ use crate::{ db::{ api::view::BoxableGraphView, - graph::{ - node::NodeView, - views::filter::{ - internal::CreateFilter, - model::{ - edge_filter::CompositeEdgeFilter, - exploded_edge_filter::CompositeExplodedEdgeFilter, - filter_operator::FilterOperator, property_filter::PropertyFilter, AndFilter, - Filter, FilterValue, NotFilter, OrFilter, PropertyFilterFactory, - TryAsCompositeFilter, Windowed, - }, + graph::views::filter::{ + internal::CreateFilter, + model::{ + edge_filter::CompositeEdgeFilter, + exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, + property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, + OrFilter, PropertyFilterFactory, TryAsCompositeFilter, Windowed, }, }, }, errors::GraphError, - prelude::{GraphViewOps, NodeViewOps}, + prelude::GraphViewOps, }; use raphtory_api::core::entities::{GidType, GID}; use raphtory_core::utils::time::IntoTime; -use raphtory_storage::{ - core_ops::CoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, -}; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; #[derive(Debug, Clone)] @@ -92,41 +84,6 @@ impl Display for CompositeNodeFilter { } } -impl CompositeNodeFilter { - pub fn matches_node<'graph, G: GraphViewOps<'graph>>( - &self, - graph: &G, - node: NodeStorageRef, - ) -> bool { - match self { - CompositeNodeFilter::Node(filter) => { - let view = NodeView::new_internal(graph, node.vid()); - match filter.field_name.as_str() { - "node_id" => filter.id_matches(view.id().as_ref()), - "node_name" => filter.matches(Some(&view.name())), - "node_type" => filter.matches(view.node_type().as_deref()), - _ => false, - } - } - CompositeNodeFilter::Property(filter) => { - let meta = graph.node_meta(); - let expect_map = false; - let Ok(prop_id) = filter.resolve_prop_id(&meta, expect_map) else { - return false; - }; - filter.matches_node(graph, prop_id, node) - } - CompositeNodeFilter::And(l, r) => { - l.matches_node(graph, node) && r.matches_node(graph, node) - } - CompositeNodeFilter::Or(l, r) => { - l.matches_node(graph, node) || r.matches_node(graph, node) - } - CompositeNodeFilter::Not(x) => !x.matches_node(graph, node), - } - } -} - impl CreateFilter for CompositeNodeFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; From edd66b7c2662a40c9a6bb0a693e9213858de2083 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:11:58 +0000 Subject: [PATCH 17/42] fix call to filter nodes --- .../src/db/graph/views/filter/edge_node_filtered_graph.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index 99d2152e2f..860ca07f44 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -18,6 +18,8 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, }; +use crate::db::api::view::internal::FilterOps; +use crate::db::graph::views::filter::model::property_filter::PropertyFilterOps; #[derive(Debug, Clone)] pub struct EdgeNodeFilteredGraph { @@ -123,7 +125,6 @@ impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InternalEdgeFilte Endpoint::Dst => dst_binding.as_ref(), }; - self.filtered_graph - .internal_filter_node(node_ref, layer_ids) + self.filtered_graph.filter_node(node_ref) } } From a6ca33acf089554e83606fe100528d798bfd9074 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:02:08 +0000 Subject: [PATCH 18/42] rid dead code --- .../src/db/graph/views/filter/model/mod.rs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 498b520b78..4623a2ef26 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -302,36 +302,6 @@ impl Filter { pub fn id_matches(&self, node_value: GidRef<'_>) -> bool { self.operator.apply_id(&self.field_value, node_value) } - - pub fn matches_edge<'graph, G: GraphViewOps<'graph>>( - &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, - }; - - 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()) - } - } - } } // Fluent Composite Filter Builder APIs From 9242e98385f18aba63e9f1e835c99ec1cd2fe62a Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Fri, 31 Oct 2025 21:17:23 -0400 Subject: [PATCH 19/42] Integrating edge endpoint filtering mechanism into Python using the same wrapper types as node filtering. --- .../views/filter/edge_node_filtered_graph.rs | 9 +- .../graph/views/filter/model/edge_filter.rs | 83 +- .../src/db/graph/views/filter/model/mod.rs | 4 +- .../graph/views/filter/model/node_filter.rs | 67 +- .../src/python/filter/edge_filter_builders.rs | 161 +--- .../src/python/filter/node_filter_builders.rs | 329 +++++++- .../python/filter/property_filter_builders.rs | 794 ++++++++++++++---- raphtory/src/python/filter/window_filter.rs | 37 +- 8 files changed, 1091 insertions(+), 393 deletions(-) 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 index 860ca07f44..c40010a136 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -3,13 +3,16 @@ use crate::{ api::{ properties::internal::InheritPropertiesOps, view::internal::{ - Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, + FilterOps, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, Static, }, }, - graph::views::filter::model::{edge_filter::Endpoint, node_filter::CompositeNodeFilter}, + graph::views::filter::model::{ + edge_filter::Endpoint, node_filter::CompositeNodeFilter, + property_filter::PropertyFilterOps, + }, }, prelude::GraphViewOps, }; @@ -18,8 +21,6 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, }; -use crate::db::api::view::internal::FilterOps; -use crate::db::graph::views::filter::model::property_filter::PropertyFilterOps; #[derive(Debug, Clone)] pub struct EdgeNodeFilteredGraph { 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 1dc8421730..15910af1ae 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -7,9 +7,10 @@ use crate::{ model::{ exploded_edge_filter::CompositeExplodedEdgeFilter, node_filter::{ - CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps, NodeIdFilter, - NodeIdFilterBuilder, NodeNameFilter, NodeNameFilterBuilder, NodeTypeFilter, - NodeTypeFilterBuilder, + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, NodeFilterBuilderOps, NodeIdFilter, + NodeIdFilterBuilder, NodeIdFilterBuilderOps, NodeNameFilter, + NodeNameFilterBuilder, NodeTypeFilter, NodeTypeFilterBuilder, }, property_filter::{ ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, @@ -162,23 +163,23 @@ impl EdgeEndpoint { impl EdgeEndpoint { #[inline] - pub fn id(self) -> EndpointWrapper { + pub fn id(&self) -> EndpointWrapper { EndpointWrapper::new(NodeFilter::id(), self.0) } #[inline] - pub fn name(self) -> EndpointWrapper { + pub fn name(&self) -> EndpointWrapper { EndpointWrapper::new(NodeFilter::name(), self.0) } #[inline] - pub fn node_type(self) -> EndpointWrapper { + pub fn node_type(&self) -> EndpointWrapper { EndpointWrapper::new(NodeFilter::node_type(), self.0) } #[inline] pub fn property( - self, + &self, name: impl Into, ) -> EndpointWrapper> { EndpointWrapper::new(PropertyFilterBuilder::::new(name), self.0) @@ -186,7 +187,7 @@ impl EdgeEndpoint { #[inline] pub fn metadata( - self, + &self, name: impl Into, ) -> EndpointWrapper> { EndpointWrapper::new(MetadataFilterBuilder::::new(name), self.0) @@ -194,7 +195,7 @@ impl EdgeEndpoint { #[inline] pub fn window( - self, + &self, start: S, end: E, ) -> EndpointWrapper> { @@ -207,7 +208,7 @@ impl EdgeEndpoint { // and at the end convert into a composite node filter via TryAsCompositeFilter #[derive(Debug, Clone)] pub struct EndpointWrapper { - inner: T, + pub(crate) inner: T, endpoint: Endpoint, } @@ -304,38 +305,38 @@ impl InternalPropertyFilterOps for EndpointWrapper impl EndpointWrapper { #[inline] - pub fn eq(self, v: impl Into) -> EndpointWrapper> { + pub fn eq(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.eq(v)) } #[inline] - pub fn ne(self, v: impl Into) -> EndpointWrapper> { + pub fn ne(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.ne(v)) } #[inline] - pub fn le(self, v: impl Into) -> EndpointWrapper> { + pub fn le(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.le(v)) } #[inline] - pub fn ge(self, v: impl Into) -> EndpointWrapper> { + pub fn ge(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.ge(v)) } #[inline] - pub fn lt(self, v: impl Into) -> EndpointWrapper> { + pub fn lt(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.lt(v)) } #[inline] - pub fn gt(self, v: impl Into) -> EndpointWrapper> { + pub fn gt(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.gt(v)) } #[inline] pub fn is_in( - self, + &self, vals: impl IntoIterator, ) -> EndpointWrapper> { self.with(self.inner.is_in(vals)) @@ -343,45 +344,45 @@ impl EndpointWrapper { #[inline] pub fn is_not_in( - self, + &self, vals: impl IntoIterator, ) -> EndpointWrapper> { self.with(self.inner.is_not_in(vals)) } #[inline] - pub fn is_none(self) -> EndpointWrapper> { + pub fn is_none(&self) -> EndpointWrapper> { self.with(self.inner.is_none()) } #[inline] - pub fn is_some(self) -> EndpointWrapper> { + pub fn is_some(&self) -> EndpointWrapper> { self.with(self.inner.is_some()) } #[inline] - pub fn starts_with(self, v: impl Into) -> EndpointWrapper> { + pub fn starts_with(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.starts_with(v)) } #[inline] - pub fn ends_with(self, v: impl Into) -> EndpointWrapper> { + pub fn ends_with(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.ends_with(v)) } #[inline] - pub fn contains(self, v: impl Into) -> EndpointWrapper> { + pub fn contains(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.contains(v)) } #[inline] - pub fn not_contains(self, v: impl Into) -> EndpointWrapper> { + pub fn not_contains(&self, v: impl Into) -> EndpointWrapper> { self.with(self.inner.not_contains(v)) } #[inline] pub fn fuzzy_search( - self, + &self, s: impl Into, d: usize, p: bool, @@ -440,7 +441,7 @@ impl EndpointWrapper> { impl EndpointWrapper> { #[inline] pub fn property( - self, + &self, name: impl Into, ) -> EndpointWrapper> { self.with(self.inner.property(name)) @@ -448,7 +449,7 @@ impl EndpointWrapper> { #[inline] pub fn metadata( - self, + &self, name: impl Into, ) -> EndpointWrapper> { self.with(self.inner.metadata(name)) @@ -616,28 +617,28 @@ impl EndpointWrapper { } #[inline] - pub fn starts_with>(self, s: S) -> EndpointWrapper { + pub fn starts_with>(&self, s: S) -> EndpointWrapper { self.with(self.inner.starts_with(s.into())) } #[inline] - pub fn ends_with>(self, s: S) -> EndpointWrapper { + pub fn ends_with>(&self, s: S) -> EndpointWrapper { self.with(self.inner.ends_with(s.into())) } #[inline] - pub fn contains>(self, s: S) -> EndpointWrapper { + pub fn contains>(&self, s: S) -> EndpointWrapper { self.with(self.inner.contains(s.into())) } #[inline] - pub fn not_contains>(self, s: S) -> EndpointWrapper { + pub fn not_contains>(&self, s: S) -> EndpointWrapper { self.with(self.inner.not_contains(s.into())) } #[inline] pub fn fuzzy_search>( - self, + &self, s: S, d: usize, p: bool, @@ -648,17 +649,17 @@ impl EndpointWrapper { impl EndpointWrapper { #[inline] - pub fn eq>(self, s: S) -> EndpointWrapper { + pub fn eq>(&self, s: S) -> EndpointWrapper { self.with(self.inner.eq(s.into())) } #[inline] - pub fn ne>(self, s: S) -> EndpointWrapper { + pub fn ne>(&self, s: S) -> EndpointWrapper { self.with(self.inner.ne(s.into())) } #[inline] - pub fn is_in(self, vals: I) -> EndpointWrapper + pub fn is_in(&self, vals: I) -> EndpointWrapper where I: IntoIterator, { @@ -666,7 +667,7 @@ impl EndpointWrapper { } #[inline] - pub fn is_not_in(self, vals: I) -> EndpointWrapper + pub fn is_not_in(&self, vals: I) -> EndpointWrapper where I: IntoIterator, { @@ -674,28 +675,28 @@ impl EndpointWrapper { } #[inline] - pub fn starts_with>(self, s: S) -> EndpointWrapper { + pub fn starts_with>(&self, s: S) -> EndpointWrapper { self.with(self.inner.starts_with(s.into())) } #[inline] - pub fn ends_with>(self, s: S) -> EndpointWrapper { + pub fn ends_with>(&self, s: S) -> EndpointWrapper { self.with(self.inner.ends_with(s.into())) } #[inline] - pub fn contains>(self, s: S) -> EndpointWrapper { + pub fn contains>(&self, s: S) -> EndpointWrapper { self.with(self.inner.contains(s.into())) } #[inline] - pub fn not_contains>(self, s: S) -> EndpointWrapper { + pub fn not_contains>(&self, s: S) -> EndpointWrapper { self.with(self.inner.not_contains(s.into())) } #[inline] pub fn fuzzy_search>( - self, + &self, s: S, d: usize, p: bool, diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 4623a2ef26..6ac55b3288 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -61,7 +61,7 @@ impl Windowed where M: Send + Sync + Clone + 'static, { - pub fn property(self, name: impl Into) -> WindowedPropertyRef { + pub fn property(&self, name: impl Into) -> WindowedPropertyRef { WindowedPropertyRef { prop_ref: PropertyRef::Property(name.into()), ops: vec![], @@ -71,7 +71,7 @@ where } } - pub fn metadata(self, name: impl Into) -> WindowedPropertyRef { + pub fn metadata(&self, name: impl Into) -> WindowedPropertyRef { WindowedPropertyRef { prop_ref: PropertyRef::Metadata(name.into()), ops: vec![], 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 d13dd50e75..6bec29adc2 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -4,10 +4,12 @@ use crate::{ graph::views::filter::{ internal::CreateFilter, model::{ - edge_filter::CompositeEdgeFilter, - exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, - property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, - OrFilter, PropertyFilterFactory, TryAsCompositeFilter, Windowed, + edge_filter::{CompositeEdgeFilter, EndpointWrapper}, + exploded_edge_filter::CompositeExplodedEdgeFilter, + filter_operator::FilterOperator, + property_filter::PropertyFilter, + AndFilter, Filter, FilterValue, NotFilter, OrFilter, PropertyFilterFactory, + TryAsCompositeFilter, Windowed, }, }, }, @@ -198,24 +200,30 @@ pub trait NodeFilterBuilderOps: InternalNodeFilterBuilderOps { impl NodeFilterBuilderOps for T {} -#[derive(Clone, Debug)] -pub struct NodeIdFilterBuilder; +pub trait InternalNodeIdFilterBuilderOps: Send + Sync { + type NodeIdFilterType: From + CreateFilter + TryAsCompositeFilter + Clone + 'static; + + fn field_name(&self) -> &'static str; +} + +impl InternalNodeIdFilterBuilderOps for Arc { + type NodeIdFilterType = T::NodeIdFilterType; -impl NodeIdFilterBuilder { - #[inline] fn field_name(&self) -> &'static str { - "node_id" + self.deref().field_name() } +} - pub fn eq>(&self, value: T) -> NodeIdFilter { +pub trait NodeIdFilterBuilderOps: InternalNodeIdFilterBuilderOps { + fn eq>(&self, value: T) -> Self::NodeIdFilterType { Filter::eq_id(self.field_name(), value).into() } - pub fn ne>(&self, value: T) -> NodeIdFilter { + fn ne>(&self, value: T) -> Self::NodeIdFilterType { Filter::ne_id(self.field_name(), value).into() } - pub fn is_in(&self, values: I) -> NodeIdFilter + fn is_in(&self, values: I) -> Self::NodeIdFilterType where I: IntoIterator, T: Into, @@ -223,7 +231,7 @@ impl NodeIdFilterBuilder { Filter::is_in_id(self.field_name(), values).into() } - pub fn is_not_in(&self, values: I) -> NodeIdFilter + fn is_not_in(&self, values: I) -> Self::NodeIdFilterType where I: IntoIterator, T: Into, @@ -231,48 +239,61 @@ impl NodeIdFilterBuilder { Filter::is_not_in_id(self.field_name(), values).into() } - pub fn lt>(&self, value: V) -> NodeIdFilter { + fn lt>(&self, value: V) -> Self::NodeIdFilterType { Filter::lt(self.field_name(), value).into() } - pub fn le>(&self, value: V) -> NodeIdFilter { + fn le>(&self, value: V) -> Self::NodeIdFilterType { Filter::le(self.field_name(), value).into() } - pub fn gt>(&self, value: V) -> NodeIdFilter { + fn gt>(&self, value: V) -> Self::NodeIdFilterType { Filter::gt(self.field_name(), value).into() } - pub fn ge>(&self, value: V) -> NodeIdFilter { + fn ge>(&self, value: V) -> Self::NodeIdFilterType { Filter::ge(self.field_name(), value).into() } - pub fn starts_with>(&self, s: S) -> NodeIdFilter { + fn starts_with>(&self, s: S) -> Self::NodeIdFilterType { Filter::starts_with(self.field_name(), s.into()).into() } - pub fn ends_with>(&self, s: S) -> NodeIdFilter { + fn ends_with>(&self, s: S) -> Self::NodeIdFilterType { Filter::ends_with(self.field_name(), s.into()).into() } - pub fn contains>(&self, s: S) -> NodeIdFilter { + fn contains>(&self, s: S) -> Self::NodeIdFilterType { Filter::contains(self.field_name(), s.into()).into() } - pub fn not_contains>(&self, s: S) -> NodeIdFilter { + fn not_contains>(&self, s: S) -> Self::NodeIdFilterType { Filter::not_contains(self.field_name(), s.into()).into() } - pub fn fuzzy_search>( + fn fuzzy_search>( &self, s: S, levenshtein_distance: usize, prefix_match: bool, - ) -> NodeIdFilter { + ) -> Self::NodeIdFilterType { Filter::fuzzy_search(self.field_name(), s, levenshtein_distance, prefix_match).into() } } +impl NodeIdFilterBuilderOps for T {} + +#[derive(Clone, Debug)] +pub struct NodeIdFilterBuilder; + +impl InternalNodeIdFilterBuilderOps for NodeIdFilterBuilder { + type NodeIdFilterType = NodeIdFilter; + #[inline] + fn field_name(&self) -> &'static str { + "node_id" + } +} + #[derive(Clone, Debug)] pub struct NodeNameFilterBuilder; diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index cc7a42d0cc..4aba256041 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -1,16 +1,15 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{ - EdgeEndpointFilter, EdgeFilter, EdgeFilterOps, EdgeIdFilterBuilder, ExplodedEdgeFilter, - InternalEdgeFilterBuilderOps, - }, + edge_filter::{EdgeEndpoint, EdgeFilter, EndpointWrapper}, + node_filter::NodeFilter, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, }, python::{ filter::{ filter_expr::PyFilterExpr, - window_filter::{py_into_millis, PyEdgeWindow, PyExplodedEdgeWindow}, + node_filter_builders::{PyNodeFilterBuilder, PyNodeIdFilterBuilder}, + window_filter::{py_into_millis, PyEdgeWindow, PyExplodedEdgeWindow, PyNodeWindow}, }, types::iterable::FromIterable, }, @@ -19,151 +18,39 @@ use pyo3::{exceptions::PyTypeError, pyclass, pymethods, Bound, PyAny, PyResult}; use raphtory_api::core::entities::GID; use std::sync::Arc; -#[pyclass(frozen, name = "EdgeIdFilterOp", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyEdgeIdFilterOp(pub EdgeIdFilterBuilder); - -#[pymethods] -impl PyEdgeIdFilterOp { - fn __eq__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.eq(value))) - } - - fn __ne__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ne(value))) - } - - fn __lt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.lt(value))) - } - - fn __le__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.le(value))) - } - - fn __gt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.gt(value))) - } - - fn __ge__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ge(value))) - } - - fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_in(values))) - } - - fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_not_in(values))) - } - - fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.starts_with(value))) - } - - fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ends_with(value))) - } - - fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.contains(value))) - } - - fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.not_contains(value))) - } - - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.fuzzy_search( - value, - levenshtein_distance, - prefix_match, - ))) - } -} - -#[pyclass(frozen, name = "EdgeFilterOp", module = "raphtory.filter")] +#[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeFilterOp(Arc); - -impl From for PyEdgeFilterOp { - fn from(value: T) -> Self { - PyEdgeFilterOp(Arc::new(value)) - } -} +pub struct PyEdgeEndpoint(pub EdgeEndpoint); #[pymethods] -impl PyEdgeFilterOp { - fn __eq__(&self, value: String) -> PyFilterExpr { - let field = self.0.eq(value); - PyFilterExpr(Arc::new(field)) - } - - fn __ne__(&self, value: String) -> PyFilterExpr { - let field = self.0.ne(value); - PyFilterExpr(Arc::new(field)) - } - - fn is_in(&self, values: FromIterable) -> PyFilterExpr { - let field = self.0.is_in(values); - PyFilterExpr(Arc::new(field)) - } - - fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - let field = self.0.is_not_in(values); - PyFilterExpr(Arc::new(field)) - } - - fn starts_with(&self, value: String) -> PyFilterExpr { - let field = self.0.starts_with(value); - PyFilterExpr(Arc::new(field)) - } - - fn ends_with(&self, value: String) -> PyFilterExpr { - let field = self.0.ends_with(value); - PyFilterExpr(Arc::new(field)) +impl PyEdgeEndpoint { + fn id(&self) -> PyNodeIdFilterBuilder { + self.0.id().into() } - fn contains(&self, value: String) -> PyFilterExpr { - let field = self.0.contains(value); - PyFilterExpr(Arc::new(field)) + fn name(&self) -> PyNodeFilterBuilder { + self.0.name().into() } - fn not_contains(&self, value: String) -> PyFilterExpr { - let field = self.0.not_contains(value); - PyFilterExpr(Arc::new(field)) + fn node_type(&self) -> PyNodeFilterBuilder { + self.0.node_type().into() } - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - let field = self - .0 - .fuzzy_search(value, levenshtein_distance, prefix_match); - PyFilterExpr(Arc::new(field)) + fn property(&self, name: String) -> EndpointWrapper> { + self.0.property(name) } -} - -#[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyEdgeEndpoint(pub EdgeEndpointFilter); -#[pymethods] -impl PyEdgeEndpoint { - fn id(&self) -> PyEdgeIdFilterOp { - PyEdgeIdFilterOp(self.0.id()) + fn metadata(&self, name: String) -> EndpointWrapper> { + self.0.metadata(name) } - fn name(&self) -> PyEdgeFilterOp { - PyEdgeFilterOp(self.0.name()) + fn window(&self, py_start: Bound, py_end: Bound) -> PyResult { + let s = py_into_millis(&py_start)?; + let e = py_into_millis(&py_end)?; + if s > e { + return Err(PyTypeError::new_err("window.start must be <= window.end")); + } + Ok(PyNodeWindow(Arc::new(self.0.window(s, e)))) } } diff --git a/raphtory/src/python/filter/node_filter_builders.rs b/raphtory/src/python/filter/node_filter_builders.rs index e33042a710..d441c2477f 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -1,7 +1,10 @@ use crate::{ db::graph::views::filter::model::{ + edge_filter::EndpointWrapper, node_filter::{ - InternalNodeFilterBuilderOps, NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, + InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, NodeFilter, + NodeFilterBuilderOps, NodeIdFilterBuilder, NodeIdFilterBuilderOps, + NodeNameFilterBuilder, NodeTypeFilterBuilder, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, @@ -28,6 +31,18 @@ impl From for PyNodeFilterBuilder } } +impl From> for PyNodeFilterBuilder { + fn from(value: EndpointWrapper) -> Self { + PyNodeFilterBuilder(Arc::new(value)) + } +} + +impl From> for PyNodeFilterBuilder { + fn from(value: EndpointWrapper) -> Self { + PyNodeFilterBuilder(Arc::new(value)) + } +} + #[pymethods] impl PyNodeFilterBuilder { fn __eq__(&self, value: String) -> PyFilterExpr { @@ -75,56 +90,68 @@ impl PyNodeFilterBuilder { #[pyclass(frozen, name = "NodeIdFilterOp", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyIdNodeFilterBuilder(Arc); +pub struct PyNodeIdFilterBuilder(Arc); + +impl From for PyNodeIdFilterBuilder { + fn from(value: T) -> Self { + PyNodeIdFilterBuilder(Arc::new(value)) + } +} + +impl From> for PyNodeIdFilterBuilder { + fn from(value: EndpointWrapper) -> Self { + PyNodeIdFilterBuilder(Arc::new(value)) + } +} #[pymethods] -impl PyIdNodeFilterBuilder { +impl PyNodeIdFilterBuilder { fn __eq__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.eq(value))) + self.0.eq(value) } fn __ne__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ne(value))) + self.0.ne(value) } fn __lt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.lt(value))) + self.0.lt(value) } fn __le__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.le(value))) + self.0.le(value) } fn __gt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.gt(value))) + self.0.gt(value) } fn __ge__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ge(value))) + self.0.ge(value) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_in(values))) + self.0.is_in(values) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_not_in(values))) + self.0.is_not_in(values) } fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.starts_with(value))) + self.0.starts_with(value) } fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ends_with(value))) + self.0.ends_with(value) } fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.contains(value))) + self.0.contains(value) } fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.not_contains(value))) + self.0.not_contains(value) } fn fuzzy_search( @@ -133,11 +160,8 @@ impl PyIdNodeFilterBuilder { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.fuzzy_search( - value, - levenshtein_distance, - prefix_match, - ))) + self.0 + .fuzzy_search(value, levenshtein_distance, prefix_match) } } @@ -152,8 +176,8 @@ impl PyNodeFilter { /// Returns: /// NodeFilterBuilder: A filter builder for filtering by node id #[staticmethod] - fn id() -> PyIdNodeFilterBuilder { - PyIdNodeFilterBuilder(Arc::new(NodeFilter::id())) + fn id() -> PyNodeIdFilterBuilder { + PyNodeIdFilterBuilder(Arc::new(NodeFilter::id())) } /// Filter node by name @@ -191,7 +215,9 @@ impl PyNodeFilter { if s > e { return Err(PyTypeError::new_err("window.start must be <= window.end")); } - Ok(PyNodeWindow(Windowed::::from_times(s, e))) + Ok(PyNodeWindow(Arc::new(Windowed::::from_times( + s, e, + )))) } } @@ -270,3 +296,260 @@ where ))) } } + +impl DynNodeFilterBuilderOps for EndpointWrapper { + fn eq(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.eq(value))) + } + + fn ne(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ne(value))) + } + + fn is_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_in(values))) + } + + fn is_not_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_not_in(values))) + } + + fn starts_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.starts_with(value))) + } + + fn ends_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ends_with(value))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.contains(value))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.not_contains(value))) + } + + fn fuzzy_search( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.fuzzy_search( + value, + levenshtein_distance, + prefix_match, + ))) + } +} + +impl DynNodeFilterBuilderOps for EndpointWrapper { + fn eq(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.eq(value))) + } + + fn ne(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ne(value))) + } + + fn is_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_in(values))) + } + + fn is_not_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_not_in(values))) + } + + fn starts_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.starts_with(value))) + } + + fn ends_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ends_with(value))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.contains(value))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.not_contains(value))) + } + + fn fuzzy_search( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.fuzzy_search( + value, + levenshtein_distance, + prefix_match, + ))) + } +} + +pub trait DynNodeIdFilterBuilderOps: Send + Sync { + fn eq(&self, value: GID) -> PyFilterExpr; + + fn ne(&self, value: GID) -> PyFilterExpr; + + fn lt(&self, value: GID) -> PyFilterExpr; + + fn le(&self, value: GID) -> PyFilterExpr; + + fn gt(&self, value: GID) -> PyFilterExpr; + + fn ge(&self, value: GID) -> PyFilterExpr; + + fn is_in(&self, values: FromIterable) -> PyFilterExpr; + + fn is_not_in(&self, values: FromIterable) -> 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( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr; +} + +impl DynNodeIdFilterBuilderOps for T +where + T: InternalNodeIdFilterBuilderOps, +{ + fn eq(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::eq(self, value))) + } + + fn ne(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::ne(self, value))) + } + + fn lt(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::lt(self, value))) + } + + fn le(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::le(self, value))) + } + + fn gt(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::gt(self, value))) + } + + fn ge(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::ge(self, value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::is_in(self, values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::is_not_in(self, values))) + } + + fn starts_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::starts_with(self, value))) + } + + fn ends_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::ends_with(self, value))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::contains(self, value))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::not_contains(self, value))) + } + + fn fuzzy_search( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(NodeIdFilterBuilderOps::fuzzy_search( + self, + value, + levenshtein_distance, + prefix_match, + ))) + } +} + +impl DynNodeIdFilterBuilderOps for EndpointWrapper { + fn eq(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.eq(value))) + } + + fn ne(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ne(value))) + } + + fn lt(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.lt(value))) + } + + fn le(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.le(value))) + } + + fn gt(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.gt(value))) + } + + fn ge(&self, value: GID) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ge(value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_in(values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.is_not_in(values))) + } + + fn starts_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.starts_with(value))) + } + + fn ends_with(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.ends_with(value))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.contains(value))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.not_contains(value))) + } + + fn fuzzy_search( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(self.fuzzy_search( + value, + levenshtein_distance, + prefix_match, + ))) + } +} diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index b8be34024d..c483d539a1 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -2,9 +2,11 @@ use crate::{ db::graph::views::filter::{ internal::CreateFilter, model::{ + edge_filter::EndpointWrapper, + node_filter::NodeFilter, property_filter::{ - ElemQualifierOps, ListAggOps, MetadataFilterBuilder, OpChainBuilder, - PropertyFilterBuilder, PropertyFilterOps, WindowedPropertyRef, + ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, + OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, WindowedPropertyRef, }, TryAsCompositeFilter, }, @@ -327,11 +329,7 @@ where } } -impl DynFilterOps for MetadataFilterBuilder -where - M: Clone + Send + Sync + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ +impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } @@ -432,26 +430,26 @@ where fn first(&self) -> PyResult { Err(PyTypeError::new_err( - "first() is only valid on temporal properties.", + "first() is only valid on temporal properties. Call temporal() first.", )) } fn last(&self) -> PyResult { Err(PyTypeError::new_err( - "last() is only valid on temporal properties.", + "last() is only valid on temporal properties. Call temporal() first.", )) } fn temporal(&self) -> PyResult { - Err(PyTypeError::new_err( - "temporal() is only valid on standard properties, not metadata.", - )) + Ok(PyPropertyFilterOps::wrap(EndpointWrapper::< + PropertyFilterBuilder, + >::temporal(self.clone()))) } } -impl DynFilterOps for OpChainBuilder +impl DynFilterOps for MetadataFilterBuilder where - M: Send + Sync + Clone + 'static, + M: Clone + Send + Sync + 'static, PropertyFilter: CreateFilter + TryAsCompositeFilter, { fn __eq__(&self, value: Prop) -> PyFilterExpr { @@ -525,124 +523,107 @@ where } fn any(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().any())) + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) } fn all(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().all())) + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) } fn len(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().len())) + Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) } fn sum(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().sum())) + Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) } fn avg(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().avg())) + Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) } fn min(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().min())) + Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) } fn max(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().max())) + Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) } fn first(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().first())) + Err(PyTypeError::new_err( + "first() is only valid on temporal properties.", + )) } fn last(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().last())) + Err(PyTypeError::new_err( + "last() is only valid on temporal properties.", + )) } fn temporal(&self) -> PyResult { Err(PyTypeError::new_err( - "temporal() must be called on a property builder, not on an existing chain.", + "temporal() is only valid on standard properties, not metadata.", )) } } -#[pyclass( - frozen, - name = "PropertyFilterOps", - module = "raphtory.filter", - subclass -)] -pub struct PyPropertyFilterOps { - ops: Arc, -} - -impl PyPropertyFilterOps { - fn wrap(t: T) -> Self { - Self { ops: Arc::new(t) } - } - - pub fn from_arc(ops: Arc) -> Self { - Self { ops } - } -} - -#[pymethods] -impl PyPropertyFilterOps { +impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { - self.ops.__eq__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - self.ops.__ne__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - self.ops.__lt__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - self.ops.__le__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - self.ops.__gt__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - self.ops.__ge__(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - self.ops.is_in(values) + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - self.ops.is_not_in(values) + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) } fn is_none(&self) -> PyFilterExpr { - self.ops.is_none() + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) } fn is_some(&self) -> PyFilterExpr { - self.ops.is_some() + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - self.ops.starts_with(value) + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - self.ops.ends_with(value) + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - self.ops.contains(value) + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - self.ops.not_contains(value) + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) } fn fuzzy_search( @@ -651,124 +632,62 @@ impl PyPropertyFilterOps { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - self.ops - .fuzzy_search(prop_value, levenshtein_distance, prefix_match) - } - - pub fn first(&self) -> PyResult { - self.ops.first() - } - - pub fn last(&self) -> PyResult { - self.ops.last() + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, + prop_value, + levenshtein_distance, + prefix_match, + ))) } - pub fn any(&self) -> PyResult { - self.ops.any() + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) } - pub fn all(&self) -> PyResult { - self.ops.all() + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) } fn len(&self) -> PyResult { - self.ops.len() + Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) } fn sum(&self) -> PyResult { - self.ops.sum() + Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) } fn avg(&self) -> PyResult { - self.ops.avg() + Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) } fn min(&self) -> PyResult { - self.ops.min() + Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) } fn max(&self) -> PyResult { - self.ops.max() - } - - pub fn temporal(&self) -> PyResult { - self.ops.temporal() + Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) } -} - -trait DynPropertyFilterBuilderOps: DynFilterOps { - fn as_dyn(self: Arc) -> Arc; -} -impl DynPropertyFilterBuilderOps for T -where - T: DynFilterOps + 'static, -{ - fn as_dyn(self: Arc) -> Arc { - self + fn first(&self) -> PyResult { + Err(PyTypeError::new_err( + "first() is only valid on temporal properties.", + )) } -} - -#[pyclass( - frozen, - name = "Property", - module = "raphtory.filter", - extends = PyPropertyFilterOps -)] -#[derive(Clone)] -pub struct PyPropertyFilterBuilder(Arc); - -impl<'py, M: Clone + Send + Sync + 'static> IntoPyObject<'py> for PropertyFilterBuilder -where - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - 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 parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); - let child = PyPropertyFilterBuilder(inner); - Bound::new(py, (child, parent)) + fn last(&self) -> PyResult { + Err(PyTypeError::new_err( + "last() is only valid on temporal properties.", + )) } -} -#[pymethods] -impl PyPropertyFilterBuilder { fn temporal(&self) -> PyResult { - self.0.temporal() - } -} - -#[pyclass( - frozen, - name = "Metadata", - module = "raphtory.filter", - extends = PyPropertyFilterOps -)] -#[derive(Clone)] -pub struct PyMetadataFilterBuilder; - -impl<'py, M: Send + Sync + Clone + 'static> IntoPyObject<'py> for MetadataFilterBuilder -where - PropertyFilter: CreateFilter + TryAsCompositeFilter, -{ - type Target = PyMetadataFilterBuilder; - type Output = Bound<'py, Self::Target>; - type Error = PyErr; - - fn into_pyobject(self, py: Python<'py>) -> Result { - let inner: Arc> = Arc::new(self); - let parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); - let child = PyMetadataFilterBuilder; - Bound::new(py, (child, parent)) + self.inner.temporal() } } -impl DynFilterOps for WindowedPropertyRef +impl DynFilterOps for OpChainBuilder where - M: Clone + Send + Sync + 'static, + M: Send + Sync + Clone + 'static, PropertyFilter: CreateFilter + TryAsCompositeFilter, { fn __eq__(&self, value: Prop) -> PyFilterExpr { @@ -811,24 +730,34 @@ where PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) } - fn starts_with(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, v))) + fn starts_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) } - fn ends_with(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, v))) + fn ends_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) } - fn contains(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, v))) + fn contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) } - fn not_contains(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, v))) + fn not_contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) } - fn fuzzy_search(&self, s: String, d: usize, p: bool) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search(self, s, d, p))) + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, + prop_value, + levenshtein_distance, + prefix_match, + ))) } fn any(&self) -> PyResult { @@ -868,6 +797,553 @@ where } fn temporal(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(self.clone().temporal())) + Err(PyTypeError::new_err( + "temporal() must be called on a property builder, not on an existing chain.", + )) + } +} + +impl DynFilterOps for EndpointWrapper> { + fn __eq__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + } + + fn __ne__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + } + + fn __lt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + } + + fn __le__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + } + + fn __gt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + } + + fn __ge__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + } + + fn is_none(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + } + + fn is_some(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + } + + fn starts_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) + } + + fn ends_with(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) + } + + fn contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) + } + + fn not_contains(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) + } + + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( + self, + prop_value, + levenshtein_distance, + prefix_match, + ))) + } + + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().any())) + } + + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().all())) + } + + fn len(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().len())) + } + + fn sum(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().sum())) + } + + fn avg(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().avg())) + } + + fn min(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().min())) + } + + fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().max())) + } + + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().first())) + } + + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().last())) + } + + fn temporal(&self) -> PyResult { + self.inner.temporal() + } +} + +impl DynFilterOps for WindowedPropertyRef +where + M: Clone + Send + Sync + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn __eq__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + } + + fn __ne__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + } + + fn __lt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + } + + fn __le__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + } + + fn __gt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + } + + fn __ge__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + } + + fn is_none(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + } + + fn is_some(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + } + + fn starts_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, v))) + } + + fn ends_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, v))) + } + + fn contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, v))) + } + + fn not_contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, v))) + } + + fn fuzzy_search(&self, s: String, d: usize, p: bool) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search(self, s, d, p))) + } + + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().any())) + } + + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().all())) + } + + fn len(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().len())) + } + + fn sum(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().sum())) + } + + fn avg(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().avg())) + } + + fn min(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().min())) + } + + fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().max())) + } + + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().first())) + } + + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().last())) + } + + fn temporal(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().temporal())) + } +} + +impl DynFilterOps for EndpointWrapper> { + fn __eq__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + } + + fn __ne__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + } + + fn __lt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + } + + fn __le__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + } + + fn __gt__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + } + + fn __ge__(&self, value: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + } + + fn is_none(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + } + + fn is_some(&self) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + } + + fn starts_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, v))) + } + + fn ends_with(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, v))) + } + + fn contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, v))) + } + + fn not_contains(&self, v: Prop) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, v))) + } + + fn fuzzy_search(&self, s: String, d: usize, p: bool) -> PyFilterExpr { + PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search(self, s, d, p))) + } + + fn any(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().any())) + } + + fn all(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().all())) + } + + fn len(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().len())) + } + + fn sum(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().sum())) + } + + fn avg(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().avg())) + } + + fn min(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().min())) + } + + fn max(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().max())) + } + + fn first(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().first())) + } + + fn last(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().last())) + } + + fn temporal(&self) -> PyResult { + Ok(PyPropertyFilterOps::wrap(self.clone().temporal())) + } +} + +#[pyclass( + frozen, + name = "PropertyFilterOps", + module = "raphtory.filter", + subclass +)] +pub struct PyPropertyFilterOps { + ops: Arc, +} + +impl PyPropertyFilterOps { + fn wrap(t: T) -> Self { + Self { ops: Arc::new(t) } + } + + pub fn from_arc(ops: Arc) -> Self { + Self { ops } + } +} + +#[pymethods] +impl PyPropertyFilterOps { + fn __eq__(&self, value: Prop) -> PyFilterExpr { + self.ops.__eq__(value) + } + + fn __ne__(&self, value: Prop) -> PyFilterExpr { + self.ops.__ne__(value) + } + + fn __lt__(&self, value: Prop) -> PyFilterExpr { + self.ops.__lt__(value) + } + + fn __le__(&self, value: Prop) -> PyFilterExpr { + self.ops.__le__(value) + } + + fn __gt__(&self, value: Prop) -> PyFilterExpr { + self.ops.__gt__(value) + } + + fn __ge__(&self, value: Prop) -> PyFilterExpr { + self.ops.__ge__(value) + } + + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + self.ops.is_in(values) + } + + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + self.ops.is_not_in(values) + } + + fn is_none(&self) -> PyFilterExpr { + self.ops.is_none() + } + + fn is_some(&self) -> PyFilterExpr { + self.ops.is_some() + } + + fn starts_with(&self, value: Prop) -> PyFilterExpr { + self.ops.starts_with(value) + } + + fn ends_with(&self, value: Prop) -> PyFilterExpr { + self.ops.ends_with(value) + } + + fn contains(&self, value: Prop) -> PyFilterExpr { + self.ops.contains(value) + } + + fn not_contains(&self, value: Prop) -> PyFilterExpr { + self.ops.not_contains(value) + } + + fn fuzzy_search( + &self, + prop_value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + self.ops + .fuzzy_search(prop_value, levenshtein_distance, prefix_match) + } + + pub fn first(&self) -> PyResult { + self.ops.first() + } + + pub fn last(&self) -> PyResult { + self.ops.last() + } + + pub fn any(&self) -> PyResult { + self.ops.any() + } + + pub fn all(&self) -> PyResult { + self.ops.all() + } + + fn len(&self) -> PyResult { + self.ops.len() + } + + fn sum(&self) -> PyResult { + self.ops.sum() + } + + fn avg(&self) -> PyResult { + self.ops.avg() + } + + fn min(&self) -> PyResult { + self.ops.min() + } + + fn max(&self) -> PyResult { + self.ops.max() + } + + pub fn temporal(&self) -> PyResult { + self.ops.temporal() + } +} + +trait DynPropertyFilterBuilderOps: DynFilterOps { + fn as_dyn(self: Arc) -> Arc; +} + +impl DynPropertyFilterBuilderOps for T +where + T: DynFilterOps + 'static, +{ + fn as_dyn(self: Arc) -> Arc { + self + } +} + +#[pyclass( + frozen, + name = "Property", + module = "raphtory.filter", + extends = PyPropertyFilterOps +)] +#[derive(Clone)] +pub struct PyPropertyFilterBuilder(Arc); + +impl<'py, M: Clone + Send + Sync + 'static> IntoPyObject<'py> for PropertyFilterBuilder +where + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + 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 parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); + let child = PyPropertyFilterBuilder(inner); + Bound::new(py, (child, parent)) + } +} + +impl<'py> IntoPyObject<'py> for EndpointWrapper> { + 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 parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); + let child = PyPropertyFilterBuilder(inner); + Bound::new(py, (child, parent)) + } +} + +#[pymethods] +impl PyPropertyFilterBuilder { + fn temporal(&self) -> PyResult { + self.0.temporal() + } +} + +#[pyclass( + frozen, + name = "Metadata", + module = "raphtory.filter", + extends = PyPropertyFilterOps +)] +#[derive(Clone)] +pub struct PyMetadataFilterBuilder; + +impl<'py, M: Send + Sync + Clone + 'static> IntoPyObject<'py> for MetadataFilterBuilder +where + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + type Target = PyMetadataFilterBuilder; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let inner: Arc> = Arc::new(self); + let parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); + let child = PyMetadataFilterBuilder; + Bound::new(py, (child, parent)) + } +} + +impl<'py> IntoPyObject<'py> for EndpointWrapper> { + type Target = PyMetadataFilterBuilder; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let inner: Arc>> = Arc::new(self); + let parent = PyPropertyFilterOps::from_arc(inner.clone().as_dyn()); + let child = PyMetadataFilterBuilder; + Bound::new(py, (child, parent)) } } diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs index 140061b6dd..9decdad385 100644 --- a/raphtory/src/python/filter/window_filter.rs +++ b/raphtory/src/python/filter/window_filter.rs @@ -1,6 +1,6 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{EdgeFilter, ExplodedEdgeFilter}, + edge_filter::{EdgeFilter, EndpointWrapper, ExplodedEdgeFilter}, node_filter::NodeFilter, property_filter::WindowedPropertyRef, Windowed, @@ -16,17 +16,46 @@ pub fn py_into_millis(obj: &Bound) -> PyResult { #[pyclass(frozen, name = "NodeWindow", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyNodeWindow(pub Windowed); +pub struct PyNodeWindow(pub Arc); #[pymethods] impl PyNodeWindow { fn property(&self, name: String) -> PyPropertyFilterOps { - let wpr: WindowedPropertyRef = self.0.clone().property(name); + self.0.property(name) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + self.0.metadata(name) + } +} + +trait DynWindowedNodeFilter: Send + Sync { + fn property(&self, name: String) -> PyPropertyFilterOps; + fn metadata(&self, name: String) -> PyPropertyFilterOps; +} + +impl DynWindowedNodeFilter for Windowed { + fn property(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.property(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + let wpr: WindowedPropertyRef = self.metadata(name); + PyPropertyFilterOps::from_arc(Arc::new(wpr)) + } +} + +impl DynWindowedNodeFilter for EndpointWrapper> { + fn property(&self, name: String) -> PyPropertyFilterOps { + let wpr: EndpointWrapper> = + EndpointWrapper::property(self, name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } fn metadata(&self, name: String) -> PyPropertyFilterOps { - let wpr: WindowedPropertyRef = self.0.clone().metadata(name); + let wpr: EndpointWrapper> = + EndpointWrapper::metadata(self, name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } } From 5902056c7acdf3cf024651662777855fba7d1a8b Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Mon, 3 Nov 2025 01:52:48 -0500 Subject: [PATCH 20/42] Added src/dst endpoint filtering support for exploded edge filters in rust and python --- .../filter/model/exploded_edge_filter.rs | 32 +++++++++++++++++++ .../src/python/filter/edge_filter_builders.rs | 11 +++++++ 2 files changed, 43 insertions(+) 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 index 31ac010068..1f7b69a2a6 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -16,9 +16,13 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; +use crate::db::graph::views::filter::edge_node_filtered_graph::EdgeNodeFilteredGraph; +use crate::db::graph::views::filter::model::edge_filter::{EdgeEndpoint, Endpoint}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeExplodedEdgeFilter { + Src(CompositeNodeFilter), + Dst(CompositeNodeFilter), Property(PropertyFilter), And( Box, @@ -34,6 +38,8 @@ pub enum CompositeExplodedEdgeFilter { 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::And(left, right) => write!(f, "({} AND {})", left, right), CompositeExplodedEdgeFilter::Or(left, right) => write!(f, "({} OR {})", left, right), @@ -66,6 +72,22 @@ impl CreateFilter for CompositeExplodedEdgeFilter { graph: G, ) -> Result, GraphError> { match self { + CompositeExplodedEdgeFilter::Src(filter) => { + let filtered_graph = filter.clone().create_filter(graph.clone())?; + Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Src, + filtered_graph + ))) + }, + CompositeExplodedEdgeFilter::Dst(filter) => { + let filtered_graph = filter.clone().create_filter(graph.clone())?; + Ok(Arc::new(EdgeNodeFilteredGraph::new( + graph, + Endpoint::Dst, + filtered_graph + ))) + }, CompositeExplodedEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeExplodedEdgeFilter::And(l, r) => Ok(Arc::new( AndFilter { @@ -109,6 +131,16 @@ pub struct ExplodedEdgeFilter; impl PropertyFilterFactory for ExplodedEdgeFilter {} impl ExplodedEdgeFilter { + #[inline] + pub fn src() -> EdgeEndpoint { + EdgeEndpoint::src() + } + + #[inline] + pub fn dst() -> EdgeEndpoint { + EdgeEndpoint::dst() + } + #[inline] pub fn window(start: S, end: E) -> Windowed { Windowed::from_times(start, end) diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 4aba256041..1a8275e453 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -17,6 +17,7 @@ use crate::{ use pyo3::{exceptions::PyTypeError, pyclass, pymethods, Bound, PyAny, PyResult}; use raphtory_api::core::entities::GID; use std::sync::Arc; +use crate::db::graph::views::filter::model::exploded_edge_filter::ExplodedEdgeFilter; #[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] #[derive(Clone)] @@ -97,6 +98,16 @@ pub struct PyExplodedEdgeFilter; #[pymethods] impl PyExplodedEdgeFilter { + #[staticmethod] + fn src() -> PyEdgeEndpoint { + PyEdgeEndpoint(EdgeFilter::src()) + } + + #[staticmethod] + fn dst() -> PyEdgeEndpoint { + PyEdgeEndpoint(EdgeFilter::dst()) + } + #[staticmethod] fn property(name: String) -> PropertyFilterBuilder { ExplodedEdgeFilter::property(name) From 0ab738a61920016ec4749b232078109fdb694588 Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Tue, 4 Nov 2025 04:17:40 -0500 Subject: [PATCH 21/42] Added src/dst endpoint filtering support for exploded edge filters in GraphQL and fixed search --- raphtory-graphql/src/model/graph/filtering.rs | 28 ++++++----------- .../filter/model/exploded_edge_filter.rs | 19 ++++++------ .../src/python/filter/edge_filter_builders.rs | 8 ++--- raphtory/src/python/filter/mod.rs | 3 +- raphtory/src/python/filter/window_filter.rs | 5 +-- raphtory/src/search/edge_filter_executor.rs | 31 +++---------------- .../search/exploded_edge_filter_executor.rs | 6 +++- 7 files changed, 37 insertions(+), 63 deletions(-) diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 8f8188e323..49f5bf6649 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -1033,32 +1033,24 @@ impl TryFrom for CompositeEdgeFilter { fn try_from(filter: EdgeFilter) -> Result { match filter { EdgeFilter::Src(src) => { - if matches!(src.field, NodeField::NodeType) { - return Err(GraphError::InvalidGqlFilter( - "Src filter does not support NODE_TYPE".into(), - )); - } - let (_, field_value, operator) = + let (field_name, field_value, operator) = translate_node_field_where(src.field, &src.where_)?; - Ok(CompositeEdgeFilter::Edge(Filter { - field_name: "src".to_string(), + let node_filter = CompositeNodeFilter::Node(Filter { + field_name, field_value, operator, - })) + }); + Ok(CompositeEdgeFilter::Src(node_filter)) } EdgeFilter::Dst(dst) => { - if matches!(dst.field, NodeField::NodeType) { - return Err(GraphError::InvalidGqlFilter( - "Dst filter does not support NODE_TYPE".into(), - )); - } - let (_, field_value, operator) = + let (field_name, field_value, operator) = translate_node_field_where(dst.field, &dst.where_)?; - Ok(CompositeEdgeFilter::Edge(Filter { - field_name: "dst".to_string(), + let node_filter = CompositeNodeFilter::Node(Filter { + field_name, field_value, operator, - })) + }); + Ok(CompositeEdgeFilter::Dst(node_filter)) } EdgeFilter::Property(p) => { if p.window.is_some() { 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 index 1f7b69a2a6..628bc6c4c0 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -2,12 +2,15 @@ use crate::{ db::{ api::view::BoxableGraphView, graph::views::filter::{ + edge_node_filtered_graph::EdgeNodeFilteredGraph, exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, internal::CreateFilter, model::{ - edge_filter::CompositeEdgeFilter, node_filter::CompositeNodeFilter, - property_filter::PropertyFilter, AndFilter, NotFilter, OrFilter, - PropertyFilterFactory, TryAsCompositeFilter, Windowed, + edge_filter::{CompositeEdgeFilter, EdgeEndpoint, Endpoint}, + node_filter::CompositeNodeFilter, + property_filter::PropertyFilter, + AndFilter, NotFilter, OrFilter, PropertyFilterFactory, TryAsCompositeFilter, + Windowed, }, }, }, @@ -16,8 +19,6 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; -use crate::db::graph::views::filter::edge_node_filtered_graph::EdgeNodeFilteredGraph; -use crate::db::graph::views::filter::model::edge_filter::{EdgeEndpoint, Endpoint}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeExplodedEdgeFilter { @@ -77,17 +78,17 @@ impl CreateFilter for CompositeExplodedEdgeFilter { Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Src, - filtered_graph + filtered_graph, ))) - }, + } CompositeExplodedEdgeFilter::Dst(filter) => { let filtered_graph = filter.clone().create_filter(graph.clone())?; Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Dst, - filtered_graph + filtered_graph, ))) - }, + } CompositeExplodedEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeExplodedEdgeFilter::And(l, r) => Ok(Arc::new( AndFilter { diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 1a8275e453..94bb2adf66 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -1,6 +1,7 @@ use crate::{ db::graph::views::filter::model::{ edge_filter::{EdgeEndpoint, EdgeFilter, EndpointWrapper}, + exploded_edge_filter::ExplodedEdgeFilter, node_filter::NodeFilter, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, @@ -17,7 +18,6 @@ use crate::{ use pyo3::{exceptions::PyTypeError, pyclass, pymethods, Bound, PyAny, PyResult}; use raphtory_api::core::entities::GID; use std::sync::Arc; -use crate::db::graph::views::filter::model::exploded_edge_filter::ExplodedEdgeFilter; #[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] #[derive(Clone)] @@ -100,14 +100,14 @@ pub struct PyExplodedEdgeFilter; impl PyExplodedEdgeFilter { #[staticmethod] fn src() -> PyEdgeEndpoint { - PyEdgeEndpoint(EdgeFilter::src()) + PyEdgeEndpoint(ExplodedEdgeFilter::src()) } #[staticmethod] fn dst() -> PyEdgeEndpoint { - PyEdgeEndpoint(EdgeFilter::dst()) + PyEdgeEndpoint(ExplodedEdgeFilter::dst()) } - + #[staticmethod] fn property(name: String) -> PropertyFilterBuilder { ExplodedEdgeFilter::property(name) diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 59e418087d..de5cb8fa9e 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -1,5 +1,5 @@ use crate::python::filter::{ - edge_filter_builders::{PyEdgeEndpoint, PyEdgeFilter, PyEdgeFilterOp, PyExplodedEdgeFilter}, + edge_filter_builders::{PyEdgeEndpoint, PyEdgeFilter, PyExplodedEdgeFilter}, filter_expr::PyFilterExpr, node_filter_builders::PyNodeFilter, property_filter_builders::{ @@ -24,7 +24,6 @@ pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { 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::()?; diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs index 9decdad385..873520ebb2 100644 --- a/raphtory/src/python/filter/window_filter.rs +++ b/raphtory/src/python/filter/window_filter.rs @@ -1,6 +1,7 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{EdgeFilter, EndpointWrapper, ExplodedEdgeFilter}, + edge_filter::{EdgeFilter, EndpointWrapper}, + exploded_edge_filter::ExplodedEdgeFilter, node_filter::NodeFilter, property_filter::WindowedPropertyRef, Windowed, @@ -29,7 +30,7 @@ impl PyNodeWindow { } } -trait DynWindowedNodeFilter: Send + Sync { +pub(crate) trait DynWindowedNodeFilter: Send + Sync { fn property(&self, name: String) -> PyPropertyFilterOps; fn metadata(&self, name: String) -> PyPropertyFilterOps; } diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 5c6c449864..3132eee0d6 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -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,12 +218,13 @@ impl<'a> EdgeFilterExecutor<'a> { offset: usize, ) -> Result>, GraphError> { match filter { + CompositeEdgeFilter::Src(_) | CompositeEdgeFilter::Dst(_) => { + // TODO: Can we use an index here to speed up search? + fallback_filter_edges(graph, filter, limit, offset) + } CompositeEdgeFilter::Property(filter) => { self.filter_property_index(graph, filter, limit, offset) } - 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 fb4664a271..38657e94c5 100644 --- a/raphtory/src/search/exploded_edge_filter_executor.rs +++ b/raphtory/src/search/exploded_edge_filter_executor.rs @@ -6,7 +6,7 @@ use crate::{ views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, + exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, property_filter::PropertyRef, }, }, @@ -219,6 +219,10 @@ 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) } From 277ce21dff9494f19751f4072f7fbe05df052bf6 Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Tue, 4 Nov 2025 04:25:53 -0500 Subject: [PATCH 22/42] Added tests from previous branch, some of them fail --- .../test_edge_endpoint_filters.py | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 python/tests/test_base_install/test_filters/test_edge_endpoint_filters.py diff --git a/python/tests/test_base_install/test_filters/test_edge_endpoint_filters.py b/python/tests/test_base_install/test_filters/test_edge_endpoint_filters.py new file mode 100644 index 0000000000..3440020dc3 --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_edge_endpoint_filters.py @@ -0,0 +1,231 @@ +from raphtory import filter +from filters_setup import init_graph, create_test_graph +from utils import with_disk_variants +import pytest + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_src_property_eq(): + def check(graph): + expr = filter.Edge.src().property("p2") == 2 + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "1"), ("2", "3")]) + assert result == expected + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_dst_property_contains(): + def check(graph): + # dst node "3" has p20 == "Gold_boat"; dst node "4" has p20 updated from Gold_boat to Gold_ship so it doesn't appear here + expr = 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_dst_property_any_contains(): + def check(graph): + # dst node "3" has p20 == "Gold_boat"; dst node "4" has p20 updated from Gold_boat to Gold_ship so it appears with .temporal().any() + expr = filter.Edge.dst().property("p20").temporal().any().contains("boat") + result = sorted(graph.filter(expr).edges.id) + expected = sorted([("2", "3"), ("3", "4")]) + assert result == expected + return check + + +@with_disk_variants(init_graph, variants=["graph", "event_disk_graph"]) +def test_edges_dst_property_gt(): + def check(graph): + expr = filter.Edge.dst().property("p100") > 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 \ No newline at end of file From 64ac8eb26a05fb9a304954b9db2a52923a9480b7 Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Tue, 4 Nov 2025 04:58:56 -0500 Subject: [PATCH 23/42] Fixed DynFilterOps implementations for EndpointWrapper types. Endpoint filtering tests pass --- .../python/filter/property_filter_builders.rs | 162 +++++++++--------- 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index c483d539a1..d771b09ffe 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -331,59 +331,59 @@ where impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + PyFilterExpr(Arc::new(self.eq(value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + PyFilterExpr(Arc::new(self.ne(value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + PyFilterExpr(Arc::new(self.lt(value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + PyFilterExpr(Arc::new(self.le(value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + PyFilterExpr(Arc::new(self.gt(value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + PyFilterExpr(Arc::new(self.ge(value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + PyFilterExpr(Arc::new(self.is_in(values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + PyFilterExpr(Arc::new(self.is_not_in(values))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + PyFilterExpr(Arc::new(self.is_none())) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + PyFilterExpr(Arc::new(self.is_some())) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) + PyFilterExpr(Arc::new(self.starts_with(value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) + PyFilterExpr(Arc::new(self.ends_with(value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) + PyFilterExpr(Arc::new(self.contains(value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) + PyFilterExpr(Arc::new(self.not_contains(value))) } fn fuzzy_search( @@ -392,8 +392,7 @@ impl DynFilterOps for EndpointWrapper> { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( - self, + PyFilterExpr(Arc::new(self.fuzzy_search( prop_value, levenshtein_distance, prefix_match, @@ -401,31 +400,38 @@ impl DynFilterOps for EndpointWrapper> { } fn any(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) + let chain = ElemQualifierOps::any(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn all(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) + let chain = ElemQualifierOps::all(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn len(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) + let chain = ListAggOps::len(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn sum(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) + let chain = ListAggOps::sum(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn avg(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) + let chain = ListAggOps::avg(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn min(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) + let chain = ListAggOps::min(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn max(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) + let chain = ListAggOps::max(self); + Ok(PyPropertyFilterOps::wrap(self.with(chain))) } fn first(&self) -> PyResult { @@ -571,59 +577,59 @@ where impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + PyFilterExpr(Arc::new(self.eq(value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + PyFilterExpr(Arc::new(self.ne(value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + PyFilterExpr(Arc::new(self.lt(value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + PyFilterExpr(Arc::new(self.le(value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + PyFilterExpr(Arc::new(self.gt(value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + PyFilterExpr(Arc::new(self.ge(value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + PyFilterExpr(Arc::new(self.is_in(values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + PyFilterExpr(Arc::new(self.is_not_in(values))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + PyFilterExpr(Arc::new(self.is_none())) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + PyFilterExpr(Arc::new(self.is_some())) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) + PyFilterExpr(Arc::new(self.starts_with(value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) + PyFilterExpr(Arc::new(self.ends_with(value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) + PyFilterExpr(Arc::new(self.contains(value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) + PyFilterExpr(Arc::new(self.not_contains(value))) } fn fuzzy_search( @@ -632,8 +638,7 @@ impl DynFilterOps for EndpointWrapper> { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( - self, + PyFilterExpr(Arc::new(self.fuzzy_search( prop_value, levenshtein_distance, prefix_match, @@ -641,31 +646,35 @@ impl DynFilterOps for EndpointWrapper> { } fn any(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::any(self))) + Ok(PyPropertyFilterOps::wrap( + self.with(ElemQualifierOps::any(self)), + )) } fn all(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ElemQualifierOps::all(self))) + Ok(PyPropertyFilterOps::wrap( + self.with(ElemQualifierOps::all(self)), + )) } fn len(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::len(self))) + Ok(PyPropertyFilterOps::wrap(self.with(ListAggOps::len(self)))) } fn sum(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::sum(self))) + Ok(PyPropertyFilterOps::wrap(self.with(ListAggOps::sum(self)))) } fn avg(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::avg(self))) + Ok(PyPropertyFilterOps::wrap(self.with(ListAggOps::avg(self)))) } fn min(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::min(self))) + Ok(PyPropertyFilterOps::wrap(self.with(ListAggOps::min(self)))) } fn max(&self) -> PyResult { - Ok(PyPropertyFilterOps::wrap(ListAggOps::max(self))) + Ok(PyPropertyFilterOps::wrap(self.with(ListAggOps::max(self)))) } fn first(&self) -> PyResult { @@ -805,59 +814,59 @@ where impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + PyFilterExpr(Arc::new(self.eq(value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + PyFilterExpr(Arc::new(self.ne(value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + PyFilterExpr(Arc::new(self.lt(value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + PyFilterExpr(Arc::new(self.le(value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + PyFilterExpr(Arc::new(self.gt(value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + PyFilterExpr(Arc::new(self.ge(value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + PyFilterExpr(Arc::new(self.is_in(values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + PyFilterExpr(Arc::new(self.is_not_in(values))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + PyFilterExpr(Arc::new(self.is_none())) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + PyFilterExpr(Arc::new(self.is_some())) } fn starts_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, value))) + PyFilterExpr(Arc::new(self.starts_with(value))) } fn ends_with(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, value))) + PyFilterExpr(Arc::new(self.ends_with(value))) } fn contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, value))) + PyFilterExpr(Arc::new(self.contains(value))) } fn not_contains(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, value))) + PyFilterExpr(Arc::new(self.not_contains(value))) } fn fuzzy_search( @@ -866,8 +875,7 @@ impl DynFilterOps for EndpointWrapper> { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search( - self, + PyFilterExpr(Arc::new(self.fuzzy_search( prop_value, levenshtein_distance, prefix_match, @@ -1023,63 +1031,63 @@ where impl DynFilterOps for EndpointWrapper> { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) + PyFilterExpr(Arc::new(self.eq(value))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ne(self, value))) + PyFilterExpr(Arc::new(self.ne(value))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::lt(self, value))) + PyFilterExpr(Arc::new(self.lt(value))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::le(self, value))) + PyFilterExpr(Arc::new(self.le(value))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::gt(self, value))) + PyFilterExpr(Arc::new(self.gt(value))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ge(self, value))) + PyFilterExpr(Arc::new(self.ge(value))) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_in(self, values))) + PyFilterExpr(Arc::new(self.is_in(values))) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_not_in(self, values))) + PyFilterExpr(Arc::new(self.is_not_in(values))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_none(self))) + PyFilterExpr(Arc::new(self.is_none())) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::is_some(self))) + PyFilterExpr(Arc::new(self.is_some())) } fn starts_with(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::starts_with(self, v))) + PyFilterExpr(Arc::new(self.starts_with(v))) } fn ends_with(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::ends_with(self, v))) + PyFilterExpr(Arc::new(self.ends_with(v))) } fn contains(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::contains(self, v))) + PyFilterExpr(Arc::new(self.contains(v))) } fn not_contains(&self, v: Prop) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::not_contains(self, v))) + PyFilterExpr(Arc::new(self.not_contains(v))) } fn fuzzy_search(&self, s: String, d: usize, p: bool) -> PyFilterExpr { - PyFilterExpr(Arc::new(PropertyFilterOps::fuzzy_search(self, s, d, p))) + PyFilterExpr(Arc::new(self.fuzzy_search(s, d, p))) } fn any(&self) -> PyResult { From a377f00ac4be8bb50883ce3ca1a5bf9baeaffc98 Mon Sep 17 00:00:00 2001 From: arienandalibi Date: Wed, 5 Nov 2025 04:22:00 -0500 Subject: [PATCH 24/42] Changed many impls to be blanket implementations using traits, especially EndpointWrapper types. Use indexes in search on edge endpoints using NodeFilterExecutor. --- .../graph/views/filter/model/edge_filter.rs | 9 +- .../src/python/filter/node_filter_builders.rs | 153 +++++++++--------- raphtory/src/python/filter/window_filter.rs | 45 +++++- raphtory/src/search/edge_filter_executor.rs | 21 ++- 4 files changed, 141 insertions(+), 87 deletions(-) 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 15910af1ae..531adac588 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -438,12 +438,15 @@ impl EndpointWrapper> { } } -impl EndpointWrapper> { +impl EndpointWrapper> +where + M: Send + Sync + Clone + 'static, +{ #[inline] pub fn property( &self, name: impl Into, - ) -> EndpointWrapper> { + ) -> EndpointWrapper> { self.with(self.inner.property(name)) } @@ -451,7 +454,7 @@ impl EndpointWrapper> { pub fn metadata( &self, name: impl Into, - ) -> EndpointWrapper> { + ) -> EndpointWrapper> { self.with(self.inner.metadata(name)) } } diff --git a/raphtory/src/python/filter/node_filter_builders.rs b/raphtory/src/python/filter/node_filter_builders.rs index d441c2477f..4ed4aeadea 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -7,7 +7,7 @@ use crate::{ NodeNameFilterBuilder, NodeTypeFilterBuilder, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - PropertyFilterFactory, Windowed, + PropertyFilterFactory, TryAsCompositeFilter, Windowed, }, python::{ filter::{ @@ -297,84 +297,58 @@ where } } -impl DynNodeFilterBuilderOps for EndpointWrapper { - fn eq(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.eq(value))) - } - - fn ne(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ne(value))) - } - - fn is_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_in(values))) - } - - fn is_not_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_not_in(values))) - } - - fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.starts_with(value))) - } - - fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ends_with(value))) - } - - fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.contains(value))) - } - - fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.not_contains(value))) - } - - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.fuzzy_search( - value, - levenshtein_distance, - prefix_match, - ))) - } -} - -impl DynNodeFilterBuilderOps for EndpointWrapper { +impl DynNodeFilterBuilderOps for EndpointWrapper +where + T: InternalNodeFilterBuilderOps + Clone + 'static, + // We will produce EndpointWrapper + T::NodeFilterType: TryAsCompositeFilter + Clone + 'static, +{ fn eq(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.eq(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::eq(&self.inner, value)), + )) } fn ne(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ne(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::ne(&self.inner, value)), + )) } fn is_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_in(values))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::is_in(&self.inner, values)), + )) } fn is_not_in(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_not_in(values))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::is_not_in(&self.inner, values)), + )) } fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.starts_with(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::starts_with(&self.inner, value)), + )) } fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ends_with(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::ends_with(&self.inner, value)), + )) } fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.contains(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::contains(&self.inner, value)), + )) } fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.not_contains(value))) + PyFilterExpr(Arc::new( + self.with(NodeFilterBuilderOps::not_contains(&self.inner, value)), + )) } fn fuzzy_search( @@ -383,11 +357,12 @@ impl DynNodeFilterBuilderOps for EndpointWrapper { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.fuzzy_search( + PyFilterExpr(Arc::new(self.with(NodeFilterBuilderOps::fuzzy_search( + &self.inner, value, levenshtein_distance, prefix_match, - ))) + )))) } } @@ -491,53 +466,82 @@ where } } -impl DynNodeIdFilterBuilderOps for EndpointWrapper { +impl DynNodeIdFilterBuilderOps for EndpointWrapper +where + T: InternalNodeIdFilterBuilderOps + Clone + 'static, + // We will produce EndpointWrapper + T::NodeIdFilterType: TryAsCompositeFilter + Clone + 'static, +{ fn eq(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.eq(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::eq(&self.inner, value)), + )) } fn ne(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ne(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::ne(&self.inner, value)), + )) } fn lt(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.lt(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::lt(&self.inner, value)), + )) } fn le(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.le(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::le(&self.inner, value)), + )) } fn gt(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.gt(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::gt(&self.inner, value)), + )) } fn ge(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ge(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::ge(&self.inner, value)), + )) } fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_in(values))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::is_in(&self.inner, values)), + )) } fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.is_not_in(values))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::is_not_in(&self.inner, values)), + )) } fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.starts_with(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::starts_with(&self.inner, value)), + )) } fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.ends_with(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::ends_with(&self.inner, value)), + )) } fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.contains(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::contains(&self.inner, value)), + )) } fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.not_contains(value))) + PyFilterExpr(Arc::new( + self.with(NodeIdFilterBuilderOps::not_contains(&self.inner, value)), + )) } fn fuzzy_search( @@ -546,10 +550,11 @@ impl DynNodeIdFilterBuilderOps for EndpointWrapper { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.fuzzy_search( + PyFilterExpr(Arc::new(self.with(NodeIdFilterBuilderOps::fuzzy_search( + &self.inner, value, levenshtein_distance, prefix_match, - ))) + )))) } } diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs index 873520ebb2..962005356c 100644 --- a/raphtory/src/python/filter/window_filter.rs +++ b/raphtory/src/python/filter/window_filter.rs @@ -10,6 +10,11 @@ use crate::{ }; use pyo3::prelude::*; use std::sync::Arc; +use crate::db::graph::views::filter::internal::CreateFilter; +use crate::db::graph::views::filter::model::{PropertyFilterFactory, TryAsCompositeFilter}; +use crate::db::graph::views::filter::model::property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}; +use crate::prelude::PropertyFilter; +use crate::python::filter::property_filter_builders::DynFilterOps; pub fn py_into_millis(obj: &Bound) -> PyResult { obj.extract::() @@ -17,7 +22,7 @@ pub fn py_into_millis(obj: &Bound) -> PyResult { #[pyclass(frozen, name = "NodeWindow", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyNodeWindow(pub Arc); +pub struct PyNodeWindow(pub Arc); #[pymethods] impl PyNodeWindow { @@ -30,37 +35,61 @@ impl PyNodeWindow { } } -pub(crate) trait DynWindowedNodeFilter: Send + Sync { +pub(crate) trait DynNodePropertyFilterFactory: Send + Sync { fn property(&self, name: String) -> PyPropertyFilterOps; fn metadata(&self, name: String) -> PyPropertyFilterOps; } -impl DynWindowedNodeFilter for Windowed { +impl DynNodePropertyFilterFactory for Windowed +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter +{ fn property(&self, name: String) -> PyPropertyFilterOps { - let wpr: WindowedPropertyRef = self.property(name); + let wpr: WindowedPropertyRef = self.property(name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } fn metadata(&self, name: String) -> PyPropertyFilterOps { - let wpr: WindowedPropertyRef = self.metadata(name); + let wpr: WindowedPropertyRef = self.metadata(name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } } -impl DynWindowedNodeFilter for EndpointWrapper> { +impl DynNodePropertyFilterFactory for EndpointWrapper> +where + M: Send + Sync + Clone + 'static, + EndpointWrapper>: DynFilterOps +{ fn property(&self, name: String) -> PyPropertyFilterOps { - let wpr: EndpointWrapper> = + let wpr: EndpointWrapper> = EndpointWrapper::property(self, name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } fn metadata(&self, name: String) -> PyPropertyFilterOps { - let wpr: EndpointWrapper> = + let wpr: EndpointWrapper> = EndpointWrapper::metadata(self, name); PyPropertyFilterOps::from_arc(Arc::new(wpr)) } } +impl DynNodePropertyFilterFactory for M +where + M: PropertyFilterFactory + Send + Sync + Clone + 'static, + PropertyFilter: CreateFilter + TryAsCompositeFilter, +{ + fn property(&self, name: String) -> PyPropertyFilterOps { + let builder: PropertyFilterBuilder = >::property(name); + PyPropertyFilterOps::from_arc(Arc::new(builder)) + } + + fn metadata(&self, name: String) -> PyPropertyFilterOps { + let builder: MetadataFilterBuilder = >::metadata(name); + PyPropertyFilterOps::from_arc(Arc::new(builder)) + } +} + #[pyclass(frozen, name = "EdgeWindow", module = "raphtory.filter")] #[derive(Clone)] pub struct PyEdgeWindow(pub Windowed); diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 3132eee0d6..5293e2f316 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -29,6 +29,8 @@ use tantivy::{ collector::Collector, query::Query, schema::Value, DocAddress, Document, IndexReader, Score, Searcher, TantivyDocument, }; +use crate::prelude::{EdgeViewOps, NodeViewOps}; +use crate::search::node_filter_executor::NodeFilterExecutor; #[derive(Clone, Copy)] pub struct EdgeFilterExecutor<'a> { @@ -218,9 +220,24 @@ impl<'a> EdgeFilterExecutor<'a> { offset: usize, ) -> Result>, GraphError> { match filter { - CompositeEdgeFilter::Src(_) | CompositeEdgeFilter::Dst(_) => { + 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) => { // TODO: Can we use an index here to speed up search? - fallback_filter_edges(graph, filter, limit, offset) + 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) From 2807c1cc221135bf2a326029024409375b8e0e81 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:54:38 +0000 Subject: [PATCH 25/42] rid dead code --- raphtory/src/db/api/properties/temporal_props.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/raphtory/src/db/api/properties/temporal_props.rs b/raphtory/src/db/api/properties/temporal_props.rs index 16962c15fb..f4936d2b66 100644 --- a/raphtory/src/db/api/properties/temporal_props.rs +++ b/raphtory/src/db/api/properties/temporal_props.rs @@ -13,7 +13,6 @@ use std::{ sync::Arc, }; -use raphtory_api::core::storage::timeindex::AsTime; #[cfg(feature = "arrow")] use {arrow_array::ArrayRef, raphtory_api::core::entities::properties::prop::PropArrayUnwrap}; @@ -92,18 +91,6 @@ impl TemporalPropertyView

{ self.props.temporal_iter(self.id) } - #[inline] - pub fn iter_window(&self, start: i64, end: i64) -> impl Iterator + '_ { - self.iter_indexed() - .filter(move |(ti, _)| ti.t() >= start && ti.t() < end) - .map(|(ti, p)| (ti.t(), p)) - } - - #[inline] - pub fn values_window(&self, start: i64, end: i64) -> impl Iterator + '_ { - self.iter_window(start, end).map(|(_, p)| p) - } - pub fn histories(&self) -> impl Iterator + '_ { self.iter() } From cd13d2f966ce8f6ffb42ce93e8e935ac16727784 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:10:12 +0000 Subject: [PATCH 26/42] nodeops suggestions from lucas --- raphtory/src/db/api/state/lazy_node_state.rs | 2 +- raphtory/src/db/api/state/ops/history.rs | 123 +++------ raphtory/src/db/api/state/ops/mod.rs | 4 +- raphtory/src/db/api/state/ops/node.rs | 267 +++++++++++++++---- raphtory/src/db/api/view/node.rs | 90 +++---- raphtory/src/db/graph/nodes.rs | 82 +++--- 6 files changed, 308 insertions(+), 260 deletions(-) diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 3249677a66..65e91b7727 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -173,7 +173,7 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra let storage = self.graph().core_graph().lock(); self.nodes .iter_refs() - .map(move |vid| self.op.apply(&storage, vid)) + .map(move |vid| self.op.apply(&self.graph(), &storage, vid)) } fn par_iter_values<'a>(&'a self) -> impl ParallelIterator> + 'a diff --git a/raphtory/src/db/api/state/ops/history.rs b/raphtory/src/db/api/state/ops/history.rs index 3d67563ed2..ba20acf040 100644 --- a/raphtory/src/db/api/state/ops/history.rs +++ b/raphtory/src/db/api/state/ops/history.rs @@ -1,140 +1,79 @@ +use std::sync::Arc; use crate::{ - db::api::{ - state::{ops::NodeOpFilter, NodeOp}, - view::internal::NodeTimeSemanticsOps, - }, - prelude::GraphViewOps, + db::api::{state::NodeOp, view::internal::NodeTimeSemanticsOps}, }; use itertools::Itertools; use raphtory_api::core::entities::VID; use raphtory_storage::graph::graph::GraphStorage; +use crate::db::api::view::internal::GraphView; #[derive(Debug, Clone)] -pub struct EarliestTime { - pub(crate) graph: G, -} +pub struct EarliestTime; -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(); + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_earliest_time(node.as_ref(), &self.graph) - } -} - -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for EarliestTime { - type Graph = G; - type Filtered + 'graph> = EarliestTime; - - fn graph(&self) -> &Self::Graph { - &self.graph + semantics.node_earliest_time(node.as_ref(), view) } - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - EarliestTime { - graph: filtered_graph, - } + fn into_dynamic(self) -> Arc> where Self: Sized { + Arc::new(self) } } #[derive(Debug, Clone)] -pub struct LatestTime { - pub(crate) graph: G, -} +pub struct LatestTime; -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(); + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_latest_time(node.as_ref(), &self.graph) - } -} - -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for LatestTime { - type Graph = G; - type Filtered + 'graph> = LatestTime; - - fn graph(&self) -> &Self::Graph { - &self.graph + semantics.node_latest_time(node.as_ref(), view) } - fn filtered + 'graph>( - &self, - filtered_graph: GH, - ) -> Self::Filtered { - LatestTime { - graph: filtered_graph, - } + fn into_dynamic(self) -> Arc> where Self: Sized { + Arc::new(self) } } #[derive(Debug, Clone)] -pub struct History { - pub(crate) graph: G, -} +pub struct History; -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(); + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = view.node_time_semantics(); let node = storage.core_node(node); semantics - .node_history(node.as_ref(), &self.graph) + .node_history(node.as_ref(), 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, - } + fn into_dynamic(self) -> Arc> where Self: Sized { + Arc::new(self) } } #[derive(Debug, Copy, Clone)] -pub struct EdgeHistoryCount { - pub(crate) graph: G, -} +pub struct EdgeHistoryCount; -impl<'graph, G: GraphViewOps<'graph>> NodeOp for EdgeHistoryCount { +impl NodeOp for EdgeHistoryCount { type Output = usize; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, view: &G, 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) - } -} - -impl<'graph, G: GraphViewOps<'graph>> NodeOpFilter<'graph> for EdgeHistoryCount { - type Graph = G; - type Filtered> = EdgeHistoryCount; - - fn graph(&self) -> &Self::Graph { - &self.graph + let ts = view.node_time_semantics(); + ts.node_edge_history_count(node.as_ref(), view) } - fn filtered>(&self, graph: GH) -> Self::Filtered { - EdgeHistoryCount { graph } + fn into_dynamic(self) -> Arc> where Self: Sized { + Arc::new(self) } } diff --git a/raphtory/src/db/api/state/ops/mod.rs b/raphtory/src/db/api/state/ops/mod.rs index 9677afc538..5994a3c847 100644 --- a/raphtory/src/db/api/state/ops/mod.rs +++ b/raphtory/src/db/api/state/ops/mod.rs @@ -1,5 +1,5 @@ -pub(crate) mod history; -pub(crate) mod node; +pub mod history; +pub mod node; mod properties; pub use history::*; diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index 24e5380346..19d74cee1d 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,3 +1,7 @@ +use crate::db::api::view::internal::GraphView; +use crate::db::graph::views::filter::model::not_filter::NotFilter; +use crate::db::graph::views::filter::model::or_filter::OrFilter; +use crate::db::graph::views::filter::model::AndFilter; use crate::{ db::api::view::internal::{ time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, @@ -15,9 +19,56 @@ use raphtory_storage::{ }; use std::{ops::Deref, sync::Arc}; +pub trait NodeFilterOp: NodeOp + Clone { + fn is_filtered(&self) -> bool; + + fn and(self, other: T) -> AndFilter; + + fn or(self, other: T) -> OrFilter; + + fn not(self) -> NotFilter; +} + +impl + Clone> NodeFilterOp for T { + 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) -> AndFilter { + AndFilter { + left: self, + right: other, + } + } + + fn or(self, other: T) -> OrFilter { + OrFilter { + left: self, + right: other, + } + } + + fn not(self) -> NotFilter { + NotFilter { 0: self } + } +} + +pub type DynNodeFilter = Arc>; + +pub struct Eq { + left: Left, + right: Right, +} + pub trait NodeOp: Send + Sync { type Output: Clone + Send + Sync; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output; + + fn const_value(&self) -> Option { + None + } + + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output; fn map(self, map: fn(Self::Output) -> V) -> Map where @@ -25,18 +76,111 @@ pub trait NodeOp: Send + Sync { { Map { op: self, map } } + + fn into_dynamic(self) -> Arc> + where + Self: Sized; +} + +impl NodeOp for Eq +where + Left: NodeOp, + Right: NodeOp, + Left::Output: PartialEq, +{ + type Output = bool; + + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(view, storage, node) == self.right.apply(view, storage, node) + } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } -// 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; +impl NodeOp for AndFilter +where + L: NodeOp, + R: NodeOp, +{ + type Output = bool; - fn graph(&self) -> &Self::Graph; + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(view, storage, node) && self.right.apply(view, storage, node) + } - fn filtered>(&self, graph: G) -> Self::Filtered; + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } +} + +impl NodeOp for OrFilter +where + L: NodeOp, + R: NodeOp, +{ + type Output = bool; + + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(view, storage, node) || self.right.apply(view, storage, node) + } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } +} + +impl NodeOp for NotFilter +where + T: NodeOp, +{ + type Output = bool; + + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + !self.0.apply(view, storage, node) + } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } +} + +#[derive(Clone, Copy)] +pub struct Const(V); + +impl NodeOp for Const +where + V: Clone, +{ + type Output = V; + + fn const_value(&self) -> Option { + self.0.clone() + } + + fn apply(&self, _view: &G, _storage: &GraphStorage, _node: VID) -> Self::Output { + self.0.clone() + } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } #[derive(Debug, Clone, Copy)] @@ -45,9 +189,16 @@ pub struct Name; impl NodeOp for Name { type Output = String; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_name(node) } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } #[derive(Debug, Copy, Clone)] @@ -56,9 +207,16 @@ pub struct Id; impl NodeOp for Id { type Output = GID; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_id(node) } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } #[derive(Debug, Copy, Clone)] @@ -66,9 +224,16 @@ pub struct Type; impl NodeOp for Type { type Output = Option; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_type(node) } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } #[derive(Debug, Copy, Clone)] @@ -76,54 +241,55 @@ pub struct TypeId; impl NodeOp for TypeId { type Output = usize; - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_type_id(node) } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) + } } #[derive(Debug, Clone)] -pub struct Degree { - pub(crate) graph: G, +pub struct Degree { pub(crate) dir: Direction, } -impl<'graph, G: GraphViewOps<'graph>> NodeOp for Degree { +impl NodeOp for Degree { type Output = usize; - fn apply(&self, storage: &GraphStorage, node: VID) -> usize { + fn apply(&self, view: &G, 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) + if matches!(view.filter_state(), FilterState::Neither) { + node.degree(view.layer_ids(), self.dir) } else { - node.filtered_neighbours_iter(&self.graph, self.graph.layer_ids(), self.dir) + node.filtered_neighbours_iter(view, view.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, - } + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) } } impl NodeOp for Arc> { type Output = V; - fn apply(&self, storage: &GraphStorage, node: VID) -> V { - self.deref().apply(storage, node) + fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> V { + self.deref().apply(view, storage, node) + } + + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + self.clone() } } @@ -136,23 +302,14 @@ pub struct Map { 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 apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + (self.map)(self.op.apply(view, storage, node)) } - fn filtered>(&self, graph: G) -> Self::Filtered { - let op = self.op.filtered(graph); - Map { op, map: self.map } + fn into_dynamic(self) -> Arc> + where + Self: Sized, + { + Arc::new(self) } } diff --git a/raphtory/src/db/api/view/node.rs b/raphtory/src/db/api/view/node.rs index cc9e021e4d..d9ca3cd3b8 100644 --- a/raphtory/src/db/api/view/node.rs +++ b/raphtory/src/db/api/view/node.rs @@ -72,31 +72,30 @@ pub trait NodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { fn node_type_id(&self) -> Self::ValueType; /// Get the timestamp for the earliest activity of the node - fn earliest_time(&self) -> Self::ValueType>; + fn earliest_time(&self) -> Self::ValueType; fn earliest_date_time( &self, - ) -> Self::ValueType, Option>>>; + ) -> Self::ValueType>>>; /// Get the timestamp for the latest activity of the node - fn latest_time(&self) -> Self::ValueType>; + fn latest_time(&self) -> Self::ValueType; - fn latest_date_time( - &self, - ) -> Self::ValueType, Option>>>; + fn latest_date_time(&self) + -> Self::ValueType>>>; /// Gets the history of the node (time that the node was added and times when changes were made to the node) - fn history(&self) -> Self::ValueType>; + fn history(&self) -> Self::ValueType; - fn edge_history_count(&self) -> Self::ValueType>; + fn edge_history_count(&self) -> Self::ValueType; /// Gets the history of the node (time that the node was added and times when changes were made to the node) as `DateTime` objects if parseable fn history_date_time( &self, - ) -> Self::ValueType, Option>>>>; + ) -> Self::ValueType>>>>; //Returns true if the node has any updates within the current window, otherwise false - fn is_active(&self) -> Self::ValueType, bool>>; + fn is_active(&self) -> Self::ValueType>; /// Get a view of the temporal properties of this node. /// @@ -113,21 +112,21 @@ pub trait NodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { /// Returns: /// /// The degree of this node. - fn degree(&self) -> Self::ValueType>; + fn degree(&self) -> Self::ValueType; /// Get the in-degree of this node (i.e., the number of edges that point into it). /// /// Returns: /// /// The in-degree of this node. - fn in_degree(&self) -> Self::ValueType>; + fn in_degree(&self) -> Self::ValueType; /// Get the out-degree of this node (i.e., the number of edges that point out of it). /// /// Returns: /// /// The out-degree of this node. - fn out_degree(&self) -> Self::ValueType>; + fn out_degree(&self) -> Self::ValueType; /// Get the edges that are incident to this node. /// @@ -202,75 +201,55 @@ 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(), - }; + fn earliest_time(&self) -> Self::ValueType { + let op = ops::EarliestTime; self.map(op) } #[inline] fn earliest_date_time( &self, - ) -> Self::ValueType, Option>>> { - let op = ops::EarliestTime { - graph: self.graph().clone(), - } - .map(|t| t.and_then(|t| t.dt())); + ) -> Self::ValueType>>> { + let op = ops::EarliestTime.map(|t| t.and_then(|t| t.dt())); self.map(op) } #[inline] - fn latest_time(&self) -> Self::ValueType> { - let op = ops::LatestTime { - graph: self.graph().clone(), - }; + fn latest_time(&self) -> Self::ValueType { + let op = ops::LatestTime; self.map(op) } #[inline] fn latest_date_time( &self, - ) -> Self::ValueType, Option>>> { - let op = ops::LatestTime { - graph: self.graph().clone(), - } - .map(|t| t.and_then(|t| t.dt())); + ) -> Self::ValueType>>> { + let op = ops::LatestTime.map(|t| t.and_then(|t| t.dt())); self.map(op) } #[inline] - fn history(&self) -> Self::ValueType> { - let op = ops::History { - graph: self.graph().clone(), - }; + fn history(&self) -> Self::ValueType { + let op = ops::History; self.map(op) } #[inline] - fn edge_history_count(&self) -> Self::ValueType> { - let op = ops::EdgeHistoryCount { - graph: self.graph().clone(), - }; + fn edge_history_count(&self) -> Self::ValueType { + let op = ops::EdgeHistoryCount; self.map(op) } #[inline] fn history_date_time( &self, - ) -> Self::ValueType, Option>>>> { - let op = ops::History { - graph: self.graph().clone(), - } - .map(|h| h.into_iter().map(|t| t.dt()).collect()); + ) -> Self::ValueType>>>> { + let op = ops::History.map(|h| h.into_iter().map(|t| t.dt()).collect()); self.map(op) } - fn is_active(&self) -> Self::ValueType, bool>> { - let op = ops::History { - graph: self.graph().clone(), - } - .map(|h| !h.is_empty()); + fn is_active(&self) -> Self::ValueType> { + let op = ops::History.map(|h| !h.is_empty()); self.map(op) } @@ -287,27 +266,22 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { } #[inline] - fn degree(&self) -> Self::ValueType> { + fn degree(&self) -> Self::ValueType { let op = ops::Degree { - graph: self.graph().clone(), dir: Direction::BOTH, }; self.map(op) } #[inline] - fn in_degree(&self) -> Self::ValueType> { - let op = ops::Degree { - graph: self.graph().clone(), - dir: Direction::IN, - }; + fn in_degree(&self) -> Self::ValueType { + let op = ops::Degree { dir: Direction::IN }; self.map(op) } #[inline] - fn out_degree(&self) -> Self::ValueType> { + fn out_degree(&self) -> Self::ValueType { let op = ops::Degree { - graph: self.graph().clone(), dir: Direction::OUT, }; self.map(op) diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 2da3645def..b7f8582201 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -1,3 +1,4 @@ +use crate::db::api::state::ops::{Const, DynNodeFilter, NodeFilterOp}; use crate::{ core::entities::{edges::edge_ref::EdgeRef, nodes::node_ref::AsNodeRef, VID}, db::{ @@ -12,6 +13,7 @@ use crate::{ }, prelude::*, }; +use raphtory_api::inherit::Base; use raphtory_storage::{ core_ops::is_view_compatible, graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, @@ -26,11 +28,10 @@ use std::{ }; #[derive(Clone)] -pub struct Nodes<'graph, G, GH = G> { +pub struct Nodes<'graph, G, F = Const> { pub(crate) base_graph: G, - pub(crate) iter_graph: GH, + pub(crate) node_select: F, pub(crate) nodes: Option>, - pub(crate) node_types_filter: Option>, _marker: PhantomData<&'graph ()>, } @@ -80,25 +81,23 @@ 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(), + node_select: self.node_select, nodes: self.nodes, - node_types_filter: self.node_types_filter, _marker: Default::default(), } } } -impl<'graph, G, GH> From> for Nodes<'graph, DynamicGraph> +impl<'graph, G, GH: NodeFilterOp> From> + for Nodes<'graph, DynamicGraph, DynNodeFilter> where G: GraphViewOps<'graph> + IntoDynamic + Static, - GH: GraphViewOps<'graph> + IntoDynamic, { fn from(value: Nodes<'graph, G, GH>) -> Self { Nodes { base_graph: value.base_graph.into_dynamic(), - iter_graph: value.iter_graph.into_dynamic(), + node_select: value.node_select.into_dynamic(), nodes: value.nodes, - node_types_filter: value.node_types_filter, _marker: PhantomData, } } @@ -111,9 +110,8 @@ where pub fn new(graph: G) -> Self { Self { base_graph: graph.clone(), - iter_graph: graph, + node_select: Const(true), nodes: None, - node_types_filter: None, _marker: PhantomData, } } @@ -122,63 +120,48 @@ where impl<'graph, G, GH> Nodes<'graph, G, GH> where G: GraphViewOps<'graph> + 'graph, - GH: GraphViewOps<'graph> + 'graph, + GH: NodeFilterOp + 'graph, { - pub fn new_filtered( - base_graph: G, - graph: GH, - nodes: Option>, - node_types_filter: Option>, - ) -> Self { + pub fn new_filtered(base_graph: G, node_select: GH, nodes: Option>) -> Self { Self { base_graph, - iter_graph: graph, + node_select, nodes, - node_types_filter, _marker: PhantomData, } } pub fn node_list(&self) -> NodeList { match self.nodes.clone() { - None => self.iter_graph.node_list(), + None => self.base_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(); + let g = self.base_graph.core_graph().lock(); + let view = self.base_graph.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()) + view.filter_node(node.as_ref()) && self.node_select.apply(view, &g, vid) }) } pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH> { Nodes::new_filtered( self.base_graph.clone(), - self.iter_graph.clone(), + self.node_select.clone(), Some(index), - self.node_types_filter.clone(), ) } #[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(); 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()) && self.node_select.apply(view, &g, vid) }) } @@ -212,7 +195,7 @@ where if self.is_list_filtered() { self.par_iter_refs().count() } else { - self.iter_graph.node_list().len() + self.base_graph.node_list().len() } } Some(nodes) => { @@ -244,11 +227,11 @@ where self.iter_graph.node_meta().node_type_meta(), node_types, )); + let type_filter = Nodes { base_graph: self.base_graph.clone(), - iter_graph: self.iter_graph.clone(), + node_select: self.node_select.clone().and(), nodes: self.nodes.clone(), - node_types_filter, _marker: PhantomData, } } @@ -259,7 +242,7 @@ where ) -> Nodes<'graph, G, GH> { let index: Index<_> = nodes .into_iter() - .filter_map(|n| self.iter_graph.node(n).map(|n| n.node)) + .filter_map(|n| self.base_graph.node(n).map(|n| n.node)) .collect(); self.indexed(index) } @@ -269,34 +252,29 @@ where } pub fn get_metadata_id(&self, prop_name: &str) -> Option { - self.iter_graph.node_meta().get_prop_id(prop_name, true) + self.base_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.base_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() + !self.base_graph.node_list_trusted() || self.node_select.is_filtered() } pub fn is_filtered(&self) -> bool { - self.node_types_filter.is_some() || self.iter_graph.filtered() + self.base_graph.filtered() || self.node_select.is_filtered() } pub fn contains(&self, node: V) -> bool { (&self.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) }) .is_some() } From ef8eefbb7fe79039b5134f57558c9564635fb34e Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 12 Nov 2025 13:26:14 +0100 Subject: [PATCH 27/42] start fixing some apis --- raphtory/src/db/api/state/group_by.rs | 4 +- raphtory/src/db/api/state/lazy_node_state.rs | 99 ++++++------- raphtory/src/db/api/state/node_state.rs | 86 ++++------- raphtory/src/db/api/state/node_state_ops.rs | 27 ++-- .../src/db/api/state/node_state_ord_ops.rs | 32 +++-- raphtory/src/db/api/state/ops/node.rs | 14 +- raphtory/src/db/graph/nodes.rs | 135 +++++++++--------- 7 files changed, 185 insertions(+), 212 deletions(-) diff --git a/raphtory/src/db/api/state/group_by.rs b/raphtory/src/db/api/state/group_by.rs index a59ceabd92..9c7eda4f16 100644 --- a/raphtory/src/db/api/state/group_by.rs +++ b/raphtory/src/db/api/state/group_by.rs @@ -115,14 +115,14 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr } pub trait NodeStateGroupBy<'graph>: NodeStateOps<'graph> { - fn groups(&self) -> NodeGroups; + fn groups(&self) -> NodeGroups; } impl<'graph, S: NodeStateOps<'graph>> NodeStateGroupBy<'graph> for S where S::OwnedValue: Hash + Eq + Debug, { - fn groups(&self) -> NodeGroups { + fn groups(&self) -> NodeGroups { self.group_by(|v| v.clone()) } } diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 65e91b7727..f92c5aef02 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -2,7 +2,10 @@ use crate::{ core::entities::{nodes::node_ref::AsNodeRef, VID}, db::{ api::{ - state::{ops::node::NodeOp, Index, NodeState, NodeStateOps}, + state::{ + ops::{Const, NodeFilterOp, NodeOp}, + Index, NodeState, NodeStateOps, + }, view::{ internal::{FilterOps, NodeList}, BoxedLIter, IntoDynBoxed, @@ -20,15 +23,20 @@ use std::{ }; #[derive(Clone)] -pub struct LazyNodeState<'graph, Op, G, GH = G> { +pub struct LazyNodeState<'graph, Op, G, GH = Const> { nodes: Nodes<'graph, G, GH>, 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: NodeFilterOp + Clone + 'graph, + RHS, + > PartialEq<&[RHS]> for LazyNodeState<'graph, O, G, GH> 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 +45,14 @@ where impl< 'graph, - Op: NodeOp + 'graph, + O: NodeOp + 'graph, G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, + GH: 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> 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 +61,13 @@ 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> + GH: NodeFilterOp + Clone + 'graph, + RHS: NodeStateOps<'graph, OwnedValue = O::Output>, + > PartialEq for LazyNodeState<'graph, O, G, GH> where - Op::Output: PartialEq, + O::Output: PartialEq, { fn eq(&self, other: &RHS) -> bool { self.len() == other.len() @@ -72,30 +80,35 @@ 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: NodeFilterOp + Clone + 'graph, + RHS, + > PartialEq> for LazyNodeState<'graph, O, G, GH> 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: NodeFilterOp + Clone, O: NodeOp + 'graph> Debug + for LazyNodeState<'graph, O, G, GH> 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: NodeFilterOp + Clone + 'graph> + IntoIterator for LazyNodeState<'graph, O, G, GH> { - type Item = (NodeView<'graph, G>, Op::Output); + type Item = (NodeView<'graph, G>, O::Output); type IntoIter = BoxedLIter<'graph, Self::Item>; fn into_iter(self) -> Self::IntoIter { @@ -107,22 +120,22 @@ 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<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> + LazyNodeState<'graph, O, G, GH> { - pub(crate) fn new(op: Op, nodes: Nodes<'graph, G, GH>) -> Self { + pub(crate) fn new(op: O, nodes: Nodes<'graph, G, GH>) -> 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, G> { if self.nodes.is_filtered() { let (keys, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = self .par_iter() @@ -130,37 +143,27 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra .unzip(); NodeState::new( self.nodes.base_graph.clone(), - self.nodes.iter_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.base_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: NodeFilterOp + Clone + 'graph> + NodeStateOps<'graph> for LazyNodeState<'graph, O, G, GH> { - type Graph = GH; + type Select = GH; type BaseGraph = G; type Value<'a> - = Op::Output + = O::Output where 'graph: 'a, Self: 'a; - type OwnedValue = Op::Output; - - fn graph(&self) -> &Self::Graph { - &self.nodes.iter_graph - } + type OwnedValue = O::Output; fn base_graph(&self) -> &Self::BaseGraph { &self.nodes.base_graph @@ -212,7 +215,7 @@ 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::Select> { self.nodes.clone() } @@ -229,10 +232,10 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra } fn get_by_index(&self, index: usize) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)> { - if self.graph().filtered() { + 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) diff --git a/raphtory/src/db/api/state/node_state.rs b/raphtory/src/db/api/state/node_state.rs index 8b65748c91..313f077629 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,25 +299,17 @@ 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 Select = Const; type Value<'a> = &'a V where 'graph: 'a; type OwnedValue = V; - fn graph(&self) -> &Self::Graph { - &self.graph - } - fn base_graph(&self) -> &Self::BaseGraph { &self.base_graph } @@ -384,13 +359,8 @@ impl< } } - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph> { - Nodes::new_filtered( - self.base_graph.clone(), - self.graph.clone(), - self.keys.clone(), - None, - ) + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Select> { + Nodes::new_filtered(self.base_graph.clone(), Const(true), self.keys.clone()) } fn par_iter<'a>( @@ -436,7 +406,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 +431,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 +438,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..afca72ee5a 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}, @@ -15,7 +18,7 @@ pub trait NodeStateOps<'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, @@ -23,8 +26,6 @@ pub trait NodeStateOps<'graph>: type OwnedValue: Clone + Send + Sync + 'graph; - fn graph(&self) -> &Self::Graph; - fn base_graph(&self) -> &Self::BaseGraph; fn iter_values<'a>(&'a self) -> impl Iterator> + 'a @@ -44,7 +45,7 @@ pub trait NodeStateOps<'graph>: where 'graph: 'a; - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph>; + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Select>; fn par_iter<'a>( &'a self, @@ -67,7 +68,7 @@ pub trait NodeStateOps<'graph>: >( &self, cmp: F, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { let mut state: Vec<_> = self .par_iter() .map(|(n, v)| (n.node, v.borrow().clone())) @@ -85,7 +86,6 @@ pub trait NodeStateOps<'graph>: NodeState::new( self.base_graph().clone(), - self.graph().clone(), values.into(), Some(Index::new(keys)), ) @@ -105,12 +105,12 @@ pub trait NodeStateOps<'graph>: >( &self, cmp: F, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { 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::BaseGraph> { self.sort_by(|(n1, _), (n2, _)| n1.id().cmp(&n2.id())) } @@ -132,7 +132,7 @@ pub trait NodeStateOps<'graph>: &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { let values = node_state_ord_ops::top_k( self.iter(), |(_, v1), (_, v2)| cmp(v1.borrow(), v2.borrow()), @@ -145,7 +145,6 @@ pub trait NodeStateOps<'graph>: NodeState::new( self.base_graph().clone(), - self.graph().clone(), values.into(), Some(Index::new(keys)), ) @@ -155,7 +154,7 @@ pub trait NodeStateOps<'graph>: &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { self.top_k_by(|v1, v2| cmp(v1, v2).reverse(), k) } @@ -195,11 +194,11 @@ pub trait NodeStateOps<'graph>: >( &self, group_fn: F, - ) -> NodeGroups { + ) -> NodeGroups { NodeGroups::new( self.par_iter() .map(|(node, v)| (node.node, group_fn(v.borrow()))), - self.graph().clone(), + self.base_graph().clone(), ) } 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..338b96de19 100644 --- a/raphtory/src/db/api/state/node_state_ord_ops.rs +++ b/raphtory/src/db/api/state/node_state_ord_ops.rs @@ -61,7 +61,7 @@ where fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -77,12 +77,13 @@ 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::BaseGraph, Self::Select>; fn bottom_k( &self, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; /// Returns a tuple of the min result with its key fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; @@ -119,7 +120,7 @@ pub trait AsOrderedNodeStateOps<'graph>: NodeStateOps<'graph> { fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -135,12 +136,13 @@ 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::BaseGraph, Self::Select>; fn bottom_k( &self, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph>; + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; /// Returns a tuple of the min result with its key fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; @@ -171,7 +173,7 @@ where fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { if reverse { self.sort_by_values_by(|a, b| a.cmp(b).reverse()) } else { @@ -179,14 +181,17 @@ 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::BaseGraph, Self::Select> { self.top_k_by(Ord::cmp, k) } fn bottom_k( &self, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { self.bottom_k_by(Ord::cmp, k) } @@ -210,7 +215,7 @@ where fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Graph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { if reverse { self.sort_by_values_by(|a, b| a.as_ord().cmp(b.as_ord()).reverse()) } else { @@ -218,14 +223,17 @@ 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::BaseGraph, Self::Select> { 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> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { self.bottom_k_by(|a, b| a.as_ord().cmp(b.as_ord()), k) } diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index 19d74cee1d..a947865d81 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,10 +1,12 @@ -use crate::db::api::view::internal::GraphView; -use crate::db::graph::views::filter::model::not_filter::NotFilter; -use crate::db::graph::views::filter::model::or_filter::OrFilter; -use crate::db::graph::views::filter::model::AndFilter; +pub mod filter; + use crate::{ - db::api::view::internal::{ - time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, + db::{ + api::view::internal::{ + time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, + GraphView, + }, + graph::views::filter::model::{not_filter::NotFilter, or_filter::OrFilter, AndFilter}, }, prelude::GraphViewOps, }; diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index b7f8582201..105b836bb4 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -1,15 +1,23 @@ -use crate::db::api::state::ops::{Const, DynNodeFilter, NodeFilterOp}; use crate::{ core::entities::{edges::edge_ref::EdgeRef, nodes::node_ref::AsNodeRef, VID}, db::{ api::{ - state::{Index, LazyNodeState, NodeOp}, + state::{ + ops::{ + filter::{FilterOp, NodeTypeFilter}, + Const, NodeFilterOp, NodeOp, + }, + Index, LazyNodeState, + }, view::{ internal::{BaseFilter, FilterOps, IterFilter, 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::model::AndFilter, + }, }, prelude::*, }; @@ -38,7 +46,7 @@ pub struct Nodes<'graph, G, F = Const> { impl< 'graph, G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph>, + GH: NodeFilterOp + Clone + 'graph, V: AsNodeRef + Hash + Eq, S: BuildHasher, > PartialEq> for Nodes<'graph, G, GH> @@ -48,8 +56,8 @@ impl< } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, V: AsNodeRef> PartialEq> - for Nodes<'graph, G, GH> +impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph, V: AsNodeRef> + PartialEq> for Nodes<'graph, G, GH> { fn eq(&self, other: &Vec) -> bool { self.iter_refs() @@ -57,7 +65,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, V: AsNodeRef> Pa } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph> + Debug> Debug +impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph + Debug> Debug for Nodes<'graph, G, GH> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -65,7 +73,9 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph> + Debug> Debug } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> PartialEq for Nodes<'graph, G, GH> { +impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> PartialEq + for Nodes<'graph, G, GH> +{ fn eq(&self, other: &Self) -> bool { if is_view_compatible(&self.base_graph, &other.base_graph) { // same storage, can use internal ids @@ -88,22 +98,22 @@ impl<'graph, G: IntoDynamic, GH: IntoDynamic> Nodes<'graph, G, GH> { } } -impl<'graph, G, GH: NodeFilterOp> From> - for Nodes<'graph, DynamicGraph, DynNodeFilter> -where - G: GraphViewOps<'graph> + IntoDynamic + Static, -{ - fn from(value: Nodes<'graph, G, GH>) -> Self { - Nodes { - base_graph: value.base_graph.into_dynamic(), - node_select: value.node_select.into_dynamic(), - nodes: value.nodes, - _marker: PhantomData, - } - } -} - -impl<'graph, G> Nodes<'graph, G, G> +// impl> From> +// for Nodes<'static, DynamicGraph, Arc>> +// where +// G: GraphViewOps<'graph> + IntoDynamic + Static, +// { +// fn from(value: Nodes<'graph, G, GH>) -> Self { +// Nodes { +// base_graph: value.base_graph.into_dynamic(), +// node_select: value.node_select.into_dynamic(), +// nodes: value.nodes, +// _marker: PhantomData, +// } +// } +// } + +impl<'graph, G> Nodes<'graph, G> where G: GraphViewOps<'graph> + Clone, { @@ -120,7 +130,7 @@ where impl<'graph, G, GH> Nodes<'graph, G, GH> where G: GraphViewOps<'graph> + 'graph, - GH: NodeFilterOp + 'graph, + GH: NodeFilterOp + Clone + 'graph, { pub fn new_filtered(base_graph: G, node_select: GH, nodes: Option>) -> Self { Self { @@ -141,9 +151,11 @@ where 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.node_select.clone(); self.node_list().into_par_iter().filter(move |&vid| { let node = g.core_node(vid); - view.filter_node(node.as_ref()) && self.node_select.apply(view, &g, vid) + view.filter_node(node.as_ref()) + && node_select.apply(&NodeView::new_internal(&view, vid)) }) } @@ -159,9 +171,11 @@ where pub(crate) fn iter_refs(&self) -> impl Iterator + Send + Sync + 'graph { let g = self.base_graph.core_graph().lock(); let view = self.base_graph.clone(); + let node_select = self.node_select.clone(); self.node_list().into_iter().filter(move |&vid| { let node = g.core_node(vid); - view.filter_node(node.as_ref()) && self.node_select.apply(view, &g, vid) + view.filter_node(node.as_ref()) + && node_select.apply(&NodeView::new_internal(&view, vid)) }) } @@ -222,15 +236,12 @@ where 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, - )); - let type_filter = + ) -> Nodes<'graph, G, AndFilter> { + let node_types_filter = NodeTypeFilter::new(node_types, &self.base_graph); + let node_select = self.node_select.clone().and(node_types_filter); Nodes { base_graph: self.base_graph.clone(), - node_select: self.node_select.clone().and(), + node_select, nodes: self.nodes.clone(), _marker: PhantomData, } @@ -259,8 +270,8 @@ where self.base_graph.node_meta().get_prop_id(prop_name, false) } - fn is_list_filtered(&self) -> bool { - !self.base_graph.node_list_trusted() || self.node_select.is_filtered() + pub fn is_list_filtered(&self) -> bool { + !self.base_graph.node_list_trusted() || self.node_select.is_filtered() } pub fn is_filtered(&self) -> bool { @@ -278,12 +289,25 @@ where }) .is_some() } + + pub fn select( + &self, + filter: Filter, + ) -> Nodes<'graph, G, AndFilter> { + let node_select = self.node_select.clone().and(filter); + Nodes { + base_graph: self.base_graph.clone(), + node_select, + nodes: self.nodes.clone(), + _marker: Default::default(), + } + } } impl<'graph, G, GH> BaseNodeViewOps<'graph> for Nodes<'graph, G, GH> where G: GraphViewOps<'graph> + 'graph, - GH: GraphViewOps<'graph> + 'graph, + GH: NodeFilterOp + Clone + 'graph, { type Graph = G; type ValueType = LazyNodeState<'graph, T, G, GH>; @@ -295,10 +319,7 @@ where &self.base_graph } - fn map(&self, op: F) -> Self::ValueType - where - ::Output: 'graph, - { + fn map(&self, op: F) -> Self::ValueType { LazyNodeState::new(op, self.clone()) } @@ -340,37 +361,10 @@ where } } -impl<'graph, Current, G> IterFilter<'graph> for Nodes<'graph, G, Current> -where - G: GraphViewOps<'graph> + 'graph, - Current: GraphViewOps<'graph> + '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, + G: NodeFilterOp + Clone + 'graph, { type BaseGraph = Current; type Filtered> = Nodes<'graph, Next, G>; @@ -385,9 +379,8 @@ where ) -> Self::Filtered { Nodes { base_graph: filtered_graph, - iter_graph: self.iter_graph.clone(), + node_select: self.node_select.clone(), nodes: self.nodes.clone(), - node_types_filter: self.node_types_filter.clone(), _marker: PhantomData, } } From afd225281e125ac23b02be6438e26ab572b92a7d Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 12 Nov 2025 14:40:27 +0100 Subject: [PATCH 28/42] fixed most of the compilation errors --- .../algorithms/components/in_components.rs | 6 +- .../algorithms/components/out_components.rs | 6 +- .../algorithms/dynamics/temporal/epidemics.rs | 1 - .../local_clustering_coefficient_batch.rs | 2 +- raphtory/src/algorithms/pathing/dijkstra.rs | 5 +- .../pathing/single_source_shortest_path.rs | 4 +- raphtory/src/db/api/state/group_by.rs | 20 +- raphtory/src/db/api/state/lazy_node_state.rs | 18 +- raphtory/src/db/api/state/node_state.rs | 2 +- raphtory/src/db/api/state/node_state_ops.rs | 22 +- .../src/db/api/state/node_state_ord_ops.rs | 52 ++--- raphtory/src/db/api/state/ops/filter.rs | 38 ++++ raphtory/src/db/api/state/ops/history.rs | 80 +++---- raphtory/src/db/api/state/ops/mod.rs | 1 + raphtory/src/db/api/state/ops/node.rs | 208 ++++++++---------- raphtory/src/db/api/view/graph.rs | 4 +- raphtory/src/db/api/view/node.rs | 90 +++++--- raphtory/src/db/graph/graph.rs | 9 +- raphtory/src/db/graph/nodes.rs | 91 ++++---- raphtory/src/python/graph/node.rs | 2 +- 20 files changed, 322 insertions(+), 339 deletions(-) create mode 100644 raphtory/src/db/api/state/ops/filter.rs diff --git a/raphtory/src/algorithms/components/in_components.rs b/raphtory/src/algorithms/components/in_components.rs index bc1860407f..e42183979f 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}, @@ -75,9 +75,8 @@ where NodeState::new_from_eval_mapped(g.clone(), local, |v| { Nodes::new_filtered( g.clone(), - g.clone(), + Const(true), Some(Index::from_iter(v.in_components)), - None, ) }) }, @@ -124,7 +123,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..8897ed72b2 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}, @@ -75,9 +75,8 @@ where NodeState::new_from_eval_mapped(g.clone(), local, |v| { Nodes::new_filtered( g.clone(), - g.clone(), + Const(true), Some(Index::from_iter(v.out_components)), - None, ) }) }, @@ -124,7 +123,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..6fdea09a3c 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,11 @@ 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(), 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..29e2bb6e92 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(), 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 9c7eda4f16..2b667e5ef6 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}, @@ -37,12 +37,7 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr self.groups.iter().map(|(v, nodes)| { ( v, - Nodes::new_filtered( - self.graph.clone(), - self.graph.clone(), - Some(nodes.clone()), - None, - ), + Nodes::new_filtered(self.graph.clone(), Const(true), Some(nodes.clone())), ) }) } @@ -83,12 +78,7 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr self.groups.get(index).map(|(v, nodes)| { ( v, - Nodes::new_filtered( - self.graph.clone(), - self.graph.clone(), - Some(nodes.clone()), - None, - ), + Nodes::new_filtered(self.graph.clone(), Const(true), Some(nodes.clone())), ) }) } @@ -115,14 +105,14 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr } pub trait NodeStateGroupBy<'graph>: NodeStateOps<'graph> { - fn groups(&self) -> NodeGroups; + fn groups(&self) -> NodeGroups; } impl<'graph, S: NodeStateOps<'graph>> NodeStateGroupBy<'graph> for S where S::OwnedValue: Hash + Eq + Debug, { - fn groups(&self) -> NodeGroups { + fn groups(&self) -> NodeGroups { self.group_by(|v| v.clone()) } } diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index f92c5aef02..4dccd775a5 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -95,7 +95,7 @@ where } } -impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone, O: NodeOp + 'graph> Debug +impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'graph, O: NodeOp + 'graph> Debug for LazyNodeState<'graph, O, G, GH> where O::Output: Debug, @@ -142,18 +142,18 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo .map(|(node, value)| (node.node, value)) .unzip(); NodeState::new( - self.nodes.base_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(), values.into(), None) + NodeState::new(self.nodes.graph.clone(), values.into(), None) } } } -impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> +impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'graph> NodeStateOps<'graph> for LazyNodeState<'graph, O, G, GH> { type Select = GH; @@ -165,8 +165,8 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo Self: 'a; type OwnedValue = O::Output; - fn base_graph(&self) -> &Self::BaseGraph { - &self.nodes.base_graph + fn graph(&self) -> &Self::BaseGraph { + &self.nodes.graph } fn iter_values<'a>(&'a self) -> impl Iterator> + 'a @@ -176,7 +176,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo let storage = self.graph().core_graph().lock(); self.nodes .iter_refs() - .map(move |vid| self.op.apply(&self.graph(), &storage, vid)) + .map(move |vid| self.op.apply(&storage, vid)) } fn par_iter_values<'a>(&'a self) -> impl ParallelIterator> + 'a @@ -247,7 +247,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo }; 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), )) } @@ -292,7 +292,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/node_state.rs b/raphtory/src/db/api/state/node_state.rs index 313f077629..8294c0624d 100644 --- a/raphtory/src/db/api/state/node_state.rs +++ b/raphtory/src/db/api/state/node_state.rs @@ -310,7 +310,7 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState 'graph: 'a; type OwnedValue = V; - fn base_graph(&self) -> &Self::BaseGraph { + fn graph(&self) -> &Self::BaseGraph { &self.base_graph } diff --git a/raphtory/src/db/api/state/node_state_ops.rs b/raphtory/src/db/api/state/node_state_ops.rs index afca72ee5a..1046d0053f 100644 --- a/raphtory/src/db/api/state/node_state_ops.rs +++ b/raphtory/src/db/api/state/node_state_ops.rs @@ -18,7 +18,7 @@ pub trait NodeStateOps<'graph>: IntoIterator, Self::OwnedValue)> + Send + Sync + 'graph { type BaseGraph: GraphViewOps<'graph>; - type Select: NodeFilterOp; + type Select: NodeFilterOp; type Value<'a>: Send + Sync + Borrow where 'graph: 'a, @@ -26,7 +26,7 @@ pub trait NodeStateOps<'graph>: type OwnedValue: Clone + Send + Sync + 'graph; - fn base_graph(&self) -> &Self::BaseGraph; + fn graph(&self) -> &Self::BaseGraph; fn iter_values<'a>(&'a self) -> impl Iterator> + 'a where @@ -73,7 +73,7 @@ pub trait NodeStateOps<'graph>: .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), @@ -84,11 +84,7 @@ pub trait NodeStateOps<'graph>: let (keys, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = state.into_par_iter().unzip(); - NodeState::new( - self.base_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. @@ -143,11 +139,7 @@ pub trait NodeStateOps<'graph>: .map(|(n, v)| (n.node, v.borrow().clone())) .unzip(); - NodeState::new( - self.base_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>( @@ -194,11 +186,11 @@ pub trait NodeStateOps<'graph>: >( &self, group_fn: F, - ) -> NodeGroups { + ) -> NodeGroups { NodeGroups::new( self.par_iter() .map(|(node, v)| (node.node, group_fn(v.borrow()))), - self.base_graph().clone(), + self.graph().clone(), ) } 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 338b96de19..766ec2440e 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,8 @@ 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::Select>; + fn sort_by_values(&self, reverse: bool) + -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -77,13 +75,9 @@ 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::Select>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; /// Returns a tuple of the min result with its key fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; @@ -117,10 +111,8 @@ 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::Select>; + fn sort_by_values(&self, reverse: bool) + -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; /// Retrieves the top-k elements from the `AlgorithmResult` based on its values. /// @@ -136,13 +128,9 @@ 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::Select>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select>; + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; /// Returns a tuple of the min result with its key fn min_item(&self) -> Option<(NodeView<&Self::BaseGraph>, Self::Value<'_>)>; @@ -173,7 +161,7 @@ where fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { if reverse { self.sort_by_values_by(|a, b| a.cmp(b).reverse()) } else { @@ -181,17 +169,11 @@ where } } - fn top_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { self.top_k_by(Ord::cmp, k) } - fn bottom_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { self.bottom_k_by(Ord::cmp, k) } @@ -215,7 +197,7 @@ where fn sort_by_values( &self, reverse: bool, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { + ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { if reverse { self.sort_by_values_by(|a, b| a.as_ord().cmp(b.as_ord()).reverse()) } else { @@ -223,17 +205,11 @@ where } } - fn top_k( - &self, - k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph, Self::Select> { + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { 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::Select> { + fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { self.bottom_k_by(|a, b| a.as_ord().cmp(b.as_ord()), k) } 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..3253618969 --- /dev/null +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -0,0 +1,38 @@ +use crate::db::api::state::{ + ops::{Const, IntoDynNodeOp}, + NodeOp, +}; +use raphtory_api::core::entities::VID; +use raphtory_storage::graph::graph::GraphStorage; +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); diff --git a/raphtory/src/db/api/state/ops/history.rs b/raphtory/src/db/api/state/ops/history.rs index ba20acf040..165318c79f 100644 --- a/raphtory/src/db/api/state/ops/history.rs +++ b/raphtory/src/db/api/state/ops/history.rs @@ -1,79 +1,79 @@ -use std::sync::Arc; -use crate::{ - db::api::{state::NodeOp, view::internal::NodeTimeSemanticsOps}, +use crate::db::api::{ + state::{ops::IntoDynNodeOp, NodeOp}, + view::internal::{GraphView, NodeTimeSemanticsOps}, }; use itertools::Itertools; use raphtory_api::core::entities::VID; use raphtory_storage::graph::graph::GraphStorage; -use crate::db::api::view::internal::GraphView; +use std::sync::Arc; #[derive(Debug, Clone)] -pub struct EarliestTime; +pub struct EarliestTime { + pub view: G, +} -impl NodeOp for EarliestTime { +impl NodeOp for EarliestTime { type Output = Option; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = view.node_time_semantics(); + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_earliest_time(node.as_ref(), view) - } - - fn into_dynamic(self) -> Arc> where Self: Sized { - Arc::new(self) + semantics.node_earliest_time(node.as_ref(), &self.view) } } +impl IntoDynNodeOp for EarliestTime {} + #[derive(Debug, Clone)] -pub struct LatestTime; +pub struct LatestTime { + pub(crate) view: G, +} -impl NodeOp for LatestTime { +impl NodeOp for LatestTime { type Output = Option; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = view.node_time_semantics(); + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); - semantics.node_latest_time(node.as_ref(), view) - } - - fn into_dynamic(self) -> Arc> where Self: Sized { - Arc::new(self) + semantics.node_latest_time(node.as_ref(), &self.view) } } +impl IntoDynNodeOp for LatestTime {} + #[derive(Debug, Clone)] -pub struct History; +pub struct History { + pub(crate) view: G, +} -impl NodeOp for History { +impl NodeOp for History { type Output = Vec; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - let semantics = view.node_time_semantics(); + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let semantics = self.view.node_time_semantics(); let node = storage.core_node(node); semantics - .node_history(node.as_ref(), view) + .node_history(node.as_ref(), &self.view) .dedup() .collect() } - - fn into_dynamic(self) -> Arc> where Self: Sized { - Arc::new(self) - } } +impl IntoDynNodeOp for History {} + #[derive(Debug, Copy, Clone)] -pub struct EdgeHistoryCount; +pub struct EdgeHistoryCount { + pub(crate) view: G, +} -impl NodeOp for EdgeHistoryCount { +impl NodeOp for EdgeHistoryCount { type Output = usize; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { let node = storage.core_node(node); - let ts = view.node_time_semantics(); - ts.node_edge_history_count(node.as_ref(), view) - } - - fn into_dynamic(self) -> Arc> where Self: Sized { - Arc::new(self) + let ts = self.view.node_time_semantics(); + ts.node_edge_history_count(node.as_ref(), &self.view) } } + +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 5994a3c847..d35734d497 100644 --- a/raphtory/src/db/api/state/ops/mod.rs +++ b/raphtory/src/db/api/state/ops/mod.rs @@ -1,3 +1,4 @@ +pub mod filter; pub mod history; pub mod node; mod properties; diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index a947865d81..c042b1d3a3 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,12 +1,16 @@ -pub mod filter; - use crate::{ db::{ - api::view::internal::{ - time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, - GraphView, + api::{ + state::ops::filter::{Mask, MaskOp}, + view::internal::{ + time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, + GraphView, + }, + }, + graph::{ + create_node_type_filter, + views::filter::model::{not_filter::NotFilter, or_filter::OrFilter, AndFilter}, }, - graph::views::filter::model::{not_filter::NotFilter, or_filter::OrFilter, AndFilter}, }, prelude::GraphViewOps, }; @@ -31,7 +35,7 @@ pub trait NodeFilterOp: NodeOp + Clone { fn not(self) -> NotFilter; } -impl + Clone> NodeFilterOp for T { +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) @@ -70,7 +74,7 @@ pub trait NodeOp: Send + Sync { None } - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output; + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output; fn map(self, map: fn(Self::Output) -> V) -> Map where @@ -78,10 +82,12 @@ pub trait NodeOp: Send + Sync { { Map { op: self, map } } +} - fn into_dynamic(self) -> Arc> - where - Self: Sized; +pub trait IntoDynNodeOp: NodeOp + Sized + 'static { + fn into_dynamic(self) -> Arc> { + Arc::new(self) + } } impl NodeOp for Eq @@ -92,18 +98,13 @@ where { type Output = bool; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - self.left.apply(view, storage, node) == self.right.apply(view, storage, node) - } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) + 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 {} + impl NodeOp for AndFilter where L: NodeOp, @@ -111,18 +112,13 @@ where { type Output = bool; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - self.left.apply(view, storage, node) && self.right.apply(view, storage, node) - } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(storage, node) && self.right.apply(storage, node) } } +impl IntoDynNodeOp for AndFilter where Self: NodeOp + 'static {} + impl NodeOp for OrFilter where L: NodeOp, @@ -130,167 +126,127 @@ where { type Output = bool; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - self.left.apply(view, storage, node) || self.right.apply(view, storage, node) - } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + self.left.apply(storage, node) || self.right.apply(storage, node) } } +impl IntoDynNodeOp for OrFilter where Self: NodeOp + 'static {} + impl NodeOp for NotFilter where T: NodeOp, { type Output = bool; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - !self.0.apply(view, storage, node) - } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + !self.0.apply(storage, node) } } -#[derive(Clone, Copy)] -pub struct Const(V); +impl IntoDynNodeOp for NotFilter where Self: NodeOp + 'static {} + +#[derive(Clone, Copy, Debug)] +pub struct Const(pub V); impl NodeOp for Const where - V: Clone, + V: Send + Sync + Clone, { type Output = V; fn const_value(&self) -> Option { - self.0.clone() + Some(self.0.clone()) } - fn apply(&self, _view: &G, _storage: &GraphStorage, _node: VID) -> Self::Output { + fn apply(&self, __storage: &GraphStorage, _node: VID) -> Self::Output { self.0.clone() } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for Const {} + #[derive(Debug, Clone, Copy)] pub struct Name; impl NodeOp for Name { type Output = String; - fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_name(node) } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for Name {} + #[derive(Debug, Copy, Clone)] pub struct Id; impl NodeOp for Id { type Output = GID; - fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_id(node) } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for Id {} + #[derive(Debug, Copy, Clone)] pub struct Type; impl NodeOp for Type { type Output = Option; - fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_type(node) } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for Type {} + #[derive(Debug, Copy, Clone)] pub struct TypeId; impl NodeOp for TypeId { type Output = usize; - fn apply(&self, _view: &G, storage: &GraphStorage, node: VID) -> Self::Output { + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { storage.node_type_id(node) } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for TypeId {} + #[derive(Debug, Clone)] -pub struct Degree { +pub struct Degree { pub(crate) dir: Direction, + pub(crate) view: G, } -impl NodeOp for Degree { +impl NodeOp for Degree { type Output = usize; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> usize { + fn apply(&self, storage: &GraphStorage, node: VID) -> usize { let node = storage.core_node(node); - if matches!(view.filter_state(), FilterState::Neither) { - node.degree(view.layer_ids(), self.dir) + if matches!(self.view.filter_state(), FilterState::Neither) { + node.degree(self.view.layer_ids(), self.dir) } else { - node.filtered_neighbours_iter(view, view.layer_ids(), self.dir) + node.filtered_neighbours_iter(&self.view, self.view.layer_ids(), self.dir) .count() } } - - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) - } } +impl IntoDynNodeOp for Degree {} + impl NodeOp for Arc> { type Output = V; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> V { - self.deref().apply(view, storage, node) + fn apply(&self, storage: &GraphStorage, node: VID) -> V { + self.deref().apply(storage, node) } +} - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { +impl IntoDynNodeOp for Arc> { + fn into_dynamic(self) -> Arc> { self.clone() } } @@ -304,14 +260,32 @@ pub struct Map { impl NodeOp for Map { type Output = V; - fn apply(&self, view: &G, storage: &GraphStorage, node: VID) -> Self::Output { - (self.map)(self.op.apply(view, storage, node)) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + (self.map)(self.op.apply(storage, node)) } +} - fn into_dynamic(self) -> Arc> - where - Self: Sized, - { - Arc::new(self) +impl IntoDynNodeOp for Map {} + +pub type NodeTypeFilter = Mask; + +impl NodeTypeFilter { + pub fn new, 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/view/graph.rs b/raphtory/src/db/api/view/graph.rs index a8209e91da..e4512e2300 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -61,7 +61,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 /// @@ -210,7 +210,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) } diff --git a/raphtory/src/db/api/view/node.rs b/raphtory/src/db/api/view/node.rs index d9ca3cd3b8..f18d34315f 100644 --- a/raphtory/src/db/api/view/node.rs +++ b/raphtory/src/db/api/view/node.rs @@ -72,30 +72,31 @@ pub trait NodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { fn node_type_id(&self) -> Self::ValueType; /// Get the timestamp for the earliest activity of the node - fn earliest_time(&self) -> Self::ValueType; + fn earliest_time(&self) -> Self::ValueType>; fn earliest_date_time( &self, - ) -> Self::ValueType>>>; + ) -> Self::ValueType, Option>>>; /// Get the timestamp for the latest activity of the node - fn latest_time(&self) -> Self::ValueType; + fn latest_time(&self) -> Self::ValueType>; - fn latest_date_time(&self) - -> Self::ValueType>>>; + fn latest_date_time( + &self, + ) -> Self::ValueType, Option>>>; /// Gets the history of the node (time that the node was added and times when changes were made to the node) - fn history(&self) -> Self::ValueType; + fn history(&self) -> Self::ValueType>; - fn edge_history_count(&self) -> Self::ValueType; + fn edge_history_count(&self) -> Self::ValueType>; /// Gets the history of the node (time that the node was added and times when changes were made to the node) as `DateTime` objects if parseable fn history_date_time( &self, - ) -> Self::ValueType>>>>; + ) -> Self::ValueType, Option>>>>; //Returns true if the node has any updates within the current window, otherwise false - fn is_active(&self) -> Self::ValueType>; + fn is_active(&self) -> Self::ValueType, bool>>; /// Get a view of the temporal properties of this node. /// @@ -112,21 +113,21 @@ pub trait NodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { /// Returns: /// /// The degree of this node. - fn degree(&self) -> Self::ValueType; + fn degree(&self) -> Self::ValueType>; /// Get the in-degree of this node (i.e., the number of edges that point into it). /// /// Returns: /// /// The in-degree of this node. - fn in_degree(&self) -> Self::ValueType; + fn in_degree(&self) -> Self::ValueType>; /// Get the out-degree of this node (i.e., the number of edges that point out of it). /// /// Returns: /// /// The out-degree of this node. - fn out_degree(&self) -> Self::ValueType; + fn out_degree(&self) -> Self::ValueType>; /// Get the edges that are incident to this node. /// @@ -201,55 +202,75 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { } #[inline] - fn earliest_time(&self) -> Self::ValueType { - let op = ops::EarliestTime; + fn earliest_time(&self) -> Self::ValueType> { + let op = ops::EarliestTime { + view: self.graph().clone(), + }; self.map(op) } #[inline] fn earliest_date_time( &self, - ) -> Self::ValueType>>> { - let op = ops::EarliestTime.map(|t| t.and_then(|t| t.dt())); + ) -> Self::ValueType, Option>>> { + let op = ops::EarliestTime { + view: self.graph().clone(), + } + .map(|t| t.and_then(|t| t.dt())); self.map(op) } #[inline] - fn latest_time(&self) -> Self::ValueType { - let op = ops::LatestTime; + fn latest_time(&self) -> Self::ValueType> { + let op = ops::LatestTime { + view: self.graph().clone(), + }; self.map(op) } #[inline] fn latest_date_time( &self, - ) -> Self::ValueType>>> { - let op = ops::LatestTime.map(|t| t.and_then(|t| t.dt())); + ) -> Self::ValueType, Option>>> { + let op = ops::LatestTime { + view: self.graph().clone(), + } + .map(|t| t.and_then(|t| t.dt())); self.map(op) } #[inline] - fn history(&self) -> Self::ValueType { - let op = ops::History; + fn history(&self) -> Self::ValueType> { + let op = ops::History { + view: self.graph().clone(), + }; self.map(op) } #[inline] - fn edge_history_count(&self) -> Self::ValueType { - let op = ops::EdgeHistoryCount; + fn edge_history_count(&self) -> Self::ValueType> { + let op = ops::EdgeHistoryCount { + view: self.graph().clone(), + }; self.map(op) } #[inline] fn history_date_time( &self, - ) -> Self::ValueType>>>> { - let op = ops::History.map(|h| h.into_iter().map(|t| t.dt()).collect()); + ) -> Self::ValueType, Option>>>> { + let op = ops::History { + view: self.graph().clone(), + } + .map(|h| h.into_iter().map(|t| t.dt()).collect()); self.map(op) } - fn is_active(&self) -> Self::ValueType> { - let op = ops::History.map(|h| !h.is_empty()); + fn is_active(&self) -> Self::ValueType, bool>> { + let op = ops::History { + view: self.graph().clone(), + } + .map(|h| !h.is_empty()); self.map(op) } @@ -266,22 +287,27 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { } #[inline] - fn degree(&self) -> Self::ValueType { + fn degree(&self) -> Self::ValueType> { let op = ops::Degree { + view: self.graph().clone(), dir: Direction::BOTH, }; self.map(op) } #[inline] - fn in_degree(&self) -> Self::ValueType { - let op = ops::Degree { dir: Direction::IN }; + fn in_degree(&self) -> Self::ValueType> { + let op = ops::Degree { + dir: Direction::IN, + view: self.graph().clone(), + }; self.map(op) } #[inline] - fn out_degree(&self) -> Self::ValueType { + fn out_degree(&self) -> Self::ValueType> { let op = ops::Degree { + view: self.graph().clone(), dir: Direction::OUT, }; self.map(op) diff --git a/raphtory/src/db/graph/graph.rs b/raphtory/src/db/graph/graph.rs index 599476a9da..7f903007fa 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::{ @@ -231,9 +232,9 @@ pub fn assert_node_equal_layer<'graph, G1: GraphViewOps<'graph>, G2: GraphViewOp pub fn assert_nodes_equal< 'graph, G1: GraphViewOps<'graph>, - GH1: GraphViewOps<'graph>, + GH1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, - GH2: GraphViewOps<'graph>, + GH2: NodeFilterOp + 'graph, >( nodes1: &Nodes<'graph, G1, GH1>, nodes2: &Nodes<'graph, G2, GH2>, @@ -244,9 +245,9 @@ pub fn assert_nodes_equal< pub fn assert_nodes_equal_layer< 'graph, G1: GraphViewOps<'graph>, - GH1: GraphViewOps<'graph>, + GH1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, - GH2: GraphViewOps<'graph>, + GH2: NodeFilterOp + 'graph, >( nodes1: &Nodes<'graph, G1, GH1>, nodes2: &Nodes<'graph, G2, GH2>, diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 105b836bb4..dfcb16254e 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -3,10 +3,7 @@ use crate::{ db::{ api::{ state::{ - ops::{ - filter::{FilterOp, NodeTypeFilter}, - Const, NodeFilterOp, NodeOp, - }, + ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp, NodeTypeFilter}, Index, LazyNodeState, }, view::{ @@ -37,7 +34,7 @@ use std::{ #[derive(Clone)] pub struct Nodes<'graph, G, F = Const> { - pub(crate) base_graph: G, + pub(crate) graph: G, pub(crate) node_select: F, pub(crate) nodes: Option>, _marker: PhantomData<&'graph ()>, @@ -77,7 +74,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> Partial for Nodes<'graph, G, GH> { 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 { @@ -87,11 +84,11 @@ impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> Partial } } -impl<'graph, G: IntoDynamic, GH: IntoDynamic> Nodes<'graph, G, GH> { - pub fn into_dyn(self) -> Nodes<'graph, DynamicGraph> { +impl Nodes<'static, G, GH> { + pub fn into_dyn(self) -> Nodes<'static, DynamicGraph, Arc>> { Nodes { - base_graph: self.base_graph.into_dynamic(), - node_select: self.node_select, + graph: self.graph.into_dynamic(), + node_select: self.node_select.into_dynamic(), nodes: self.nodes, _marker: Default::default(), } @@ -119,7 +116,7 @@ where { pub fn new(graph: G) -> Self { Self { - base_graph: graph.clone(), + graph: graph.clone(), node_select: Const(true), nodes: None, _marker: PhantomData, @@ -134,7 +131,7 @@ where { pub fn new_filtered(base_graph: G, node_select: GH, nodes: Option>) -> Self { Self { - base_graph, + graph: base_graph, node_select, nodes, _marker: PhantomData, @@ -143,49 +140,43 @@ where pub fn node_list(&self) -> NodeList { match self.nodes.clone() { - None => self.base_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.base_graph.core_graph().lock(); - let view = self.base_graph.clone(); + let g = self.graph.core_graph().lock(); + let view = self.graph.clone(); let node_select = self.node_select.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(&NodeView::new_internal(&view, vid)) + view.filter_node(node.as_ref()) && node_select.apply(&g, vid) }) } pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH> { - Nodes::new_filtered( - self.base_graph.clone(), - self.node_select.clone(), - Some(index), - ) + Nodes::new_filtered(self.graph.clone(), self.node_select.clone(), Some(index)) } #[inline] pub(crate) fn iter_refs(&self) -> impl Iterator + Send + Sync + 'graph { - let g = self.base_graph.core_graph().lock(); - let view = self.base_graph.clone(); + let g = self.graph.core_graph().lock(); + let view = self.graph.clone(); let node_select = self.node_select.clone(); self.node_list().into_iter().filter(move |&vid| { let node = g.core_node(vid); - view.filter_node(node.as_ref()) - && node_select.apply(&NodeView::new_internal(&view, vid)) + view.filter_node(node.as_ref()) && node_select.apply(&g, vid) }) } pub fn iter(&self) -> impl Iterator> + use<'_, 'graph, G, GH> { 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(); + let base_graph = self.graph.clone(); self.iter_refs() .map(move |v| NodeView::new_internal(base_graph.clone(), v)) .into_dyn_boxed() @@ -193,12 +184,12 @@ where pub fn par_iter(&self) -> impl ParallelIterator> + use<'_, 'graph, G, GH> { 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 { 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. @@ -209,7 +200,7 @@ where if self.is_list_filtered() { self.par_iter_refs().count() } else { - self.base_graph.node_list().len() + self.graph.node_list().len() } } Some(nodes) => { @@ -228,19 +219,19 @@ where } pub fn get(&self, node: V) -> Option> { - let vid = self.base_graph.internalise_node(node.as_node_ref())?; + 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, AndFilter> { - let node_types_filter = NodeTypeFilter::new(node_types, &self.base_graph); + let node_types_filter = NodeTypeFilter::new(node_types, &self.graph); let node_select = self.node_select.clone().and(node_types_filter); Nodes { - base_graph: self.base_graph.clone(), + graph: self.graph.clone(), node_select, nodes: self.nodes.clone(), _marker: PhantomData, @@ -253,7 +244,7 @@ where ) -> Nodes<'graph, G, GH> { let index: Index<_> = nodes .into_iter() - .filter_map(|n| self.base_graph.node(n).map(|n| n.node)) + .filter_map(|n| self.graph.node(n).map(|n| n.node)) .collect(); self.indexed(index) } @@ -263,19 +254,19 @@ where } pub fn get_metadata_id(&self, prop_name: &str) -> Option { - self.base_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.base_graph.node_meta().get_prop_id(prop_name, false) + self.graph.node_meta().get_prop_id(prop_name, false) } pub fn is_list_filtered(&self) -> bool { - !self.base_graph.node_list_trusted() || self.node_select.is_filtered() + !self.graph.node_list_trusted() || self.node_select.is_filtered() } pub fn is_filtered(&self) -> bool { - self.base_graph.filtered() || self.node_select.is_filtered() + self.graph.filtered() || self.node_select.is_filtered() } pub fn contains(&self, node: V) -> bool { @@ -296,7 +287,7 @@ where ) -> Nodes<'graph, G, AndFilter> { let node_select = self.node_select.clone().and(filter); Nodes { - base_graph: self.base_graph.clone(), + graph: self.graph.clone(), node_select, nodes: self.nodes.clone(), _marker: Default::default(), @@ -307,7 +298,7 @@ where impl<'graph, G, GH> BaseNodeViewOps<'graph> for Nodes<'graph, G, GH> where G: GraphViewOps<'graph> + 'graph, - GH: NodeFilterOp + Clone + 'graph, + GH: NodeFilterOp + 'graph, { type Graph = G; type ValueType = LazyNodeState<'graph, T, G, GH>; @@ -316,7 +307,7 @@ where type Edges = NestedEdges<'graph, G>; fn graph(&self) -> &Self::Graph { - &self.base_graph + &self.graph } fn map(&self, op: F) -> Self::ValueType { @@ -330,7 +321,7 @@ where &self, op: F, ) -> 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| { @@ -338,7 +329,7 @@ where op(cg, &graph, node).into_dyn_boxed() }); NestedEdges { - base_graph: self.base_graph.clone(), + base_graph: self.graph.clone(), nodes, edges, } @@ -351,10 +342,10 @@ where &self, op: F, ) -> 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() }) @@ -370,7 +361,7 @@ where type Filtered> = Nodes<'graph, Next, G>; fn base_graph(&self) -> &Self::BaseGraph { - &self.base_graph + &self.graph } fn apply_filter>( @@ -378,7 +369,7 @@ where filtered_graph: Next, ) -> Self::Filtered { Nodes { - base_graph: filtered_graph, + graph: filtered_graph, node_select: self.node_select.clone(), nodes: self.nodes.clone(), _marker: PhantomData, @@ -389,7 +380,7 @@ where impl<'graph, G, GH> IntoIterator for Nodes<'graph, G, GH> where G: GraphViewOps<'graph> + 'graph, - GH: GraphViewOps<'graph> + 'graph, + GH: NodeFilterOp + 'graph, { type Item = NodeView<'graph, G>; type IntoIter = BoxedLIter<'graph, Self::Item>; diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 1a84f47465..dc87fe18f5 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -491,7 +491,7 @@ impl { fn from(value: Nodes<'static, G, GH>) -> Self { let graph = value.iter_graph.into_dynamic(); - let base_graph = value.base_graph.into_dynamic(); + let base_graph = value.graph.into_dynamic(); Self { nodes: Nodes::new_filtered(base_graph, graph, value.nodes, value.node_types_filter), } From d825be975d561875c28193deb17aa91df406dd92 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 12 Nov 2025 17:47:18 +0100 Subject: [PATCH 29/42] fix all the python problems except for actually implementing the python filtering --- raphtory-api/src/core/entities/mod.rs | 6 +- raphtory/src/db/api/state/lazy_node_state.rs | 15 +++- raphtory/src/db/api/state/ops/node.rs | 2 + raphtory/src/db/graph/nodes.rs | 20 +++-- .../src/db/graph/views/filter/internal.rs | 23 ++++- .../filter/node_property_filtered_graph.rs | 38 ++++++-- raphtory/src/python/graph/node.rs | 88 ++++++++++++++----- .../src/python/graph/node_state/node_state.rs | 51 ++++++----- raphtory/src/python/packages/algorithms.rs | 7 +- raphtory/src/python/types/repr.rs | 12 +-- 10 files changed, 187 insertions(+), 75 deletions(-) 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/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 4dccd775a5..2f92d025e0 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -3,12 +3,12 @@ use crate::{ db::{ api::{ state::{ - ops::{Const, NodeFilterOp, NodeOp}, + ops::{Const, DynNodeFilter, DynNodeOp, IntoDynNodeOp, NodeFilterOp, NodeOp}, Index, NodeState, NodeStateOps, }, view::{ internal::{FilterOps, NodeList}, - BoxedLIter, IntoDynBoxed, + BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, }, }, graph::{node::NodeView, nodes::Nodes}, @@ -120,6 +120,17 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo } } +impl + LazyNodeState<'static, O, G, GH> +{ + pub fn into_dyn(self) -> LazyNodeState<'static, O, DynamicGraph, DynNodeFilter> { + LazyNodeState { + nodes: self.nodes.into_dyn(), + op: self.op, + } + } +} + impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> LazyNodeState<'graph, O, G, GH> { diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index c042b1d3a3..a695210a60 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -90,6 +90,8 @@ pub trait IntoDynNodeOp: NodeOp + Sized + 'static { } } +pub type DynNodeOp = Arc>; + impl NodeOp for Eq where Left: NodeOp, diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index dfcb16254e..eca05ac130 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -12,10 +12,13 @@ use crate::{ }, }, graph::{ - edges::NestedEdges, node::NodeView, path::PathFromGraph, - views::filter::model::AndFilter, + edges::NestedEdges, + node::NodeView, + path::PathFromGraph, + views::filter::{internal::CreateNodeFilter, model::AndFilter}, }, }, + errors::GraphError, prelude::*, }; use raphtory_api::inherit::Base; @@ -281,17 +284,20 @@ where .is_some() } - pub fn select( + pub fn select( &self, filter: Filter, - ) -> Nodes<'graph, G, AndFilter> { - let node_select = self.node_select.clone().and(filter); - Nodes { + ) -> Result>>, GraphError> { + let node_select = self + .node_select + .clone() + .and(filter.create_node_filter(self.graph.clone())?); + Ok(Nodes { graph: self.graph.clone(), node_select, nodes: self.nodes.clone(), _marker: Default::default(), - } + }) } } diff --git a/raphtory/src/db/graph/views/filter/internal.rs b/raphtory/src/db/graph/views/filter/internal.rs index 18748ddc57..4e8f4c18d5 100644 --- a/raphtory/src/db/graph/views/filter/internal.rs +++ b/raphtory/src/db/graph/views/filter/internal.rs @@ -1,4 +1,8 @@ -use crate::{errors::GraphError, prelude::GraphViewOps}; +use crate::{ + db::api::{state::ops::NodeFilterOp, view::internal::GraphView}, + errors::GraphError, + prelude::GraphViewOps, +}; pub trait CreateFilter: Sized { type EntityFiltered<'graph, G>: GraphViewOps<'graph> @@ -11,3 +15,20 @@ pub trait CreateFilter: Sized { graph: G, ) -> Result, GraphError>; } + +pub trait CreateNodeFilter: Sized { + type NodeFilter: NodeFilterOp; + fn create_node_filter(self, graph: G) -> Result, GraphError>; +} + +// Not sure if this is useful +impl CreateNodeFilter for T { + type NodeFilter = T; + + fn create_node_filter( + self, + _graph: G, + ) -> Result, GraphError> { + Ok(self) + } +} 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..f0a0f74611 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,15 +2,16 @@ 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, + internal::{CreateFilter, CreateNodeFilter}, model::{node_filter::NodeFilter, property_filter::PropertyFilter, Windowed}, }, window_graph::WindowedGraph, @@ -20,10 +21,16 @@ use crate::{ prelude::{GraphViewOps, TimeOps}, }; use raphtory_api::{ - core::{entities::LayerIds, storage::timeindex::AsTime}, + core::{ + entities::{LayerIds, VID}, + storage::timeindex::AsTime, + }, inherit::Base, }; -use raphtory_storage::{core_ops::InheritCoreGraphOps, graph::nodes::node_ref::NodeStorageRef}; +use raphtory_storage::{ + core_ops::{CoreGraphOps, InheritCoreGraphOps}, + graph::{graph::GraphStorage, nodes::node_ref::NodeStorageRef}, +}; #[derive(Debug, Clone)] pub struct NodePropertyFilteredGraph { @@ -78,6 +85,25 @@ impl CreateFilter for PropertyFilter { } } +impl NodeOp for NodePropertyFilteredGraph { + type Output = bool; + + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let layer_ids = self.graph.layer_ids(); + let nodes = storage.nodes(); + let node_ref = nodes.node(node); + self.internal_filter_node(node_ref, layer_ids) + } +} + +impl CreateNodeFilter for PropertyFilter { + type NodeFilter = NodePropertyFilteredGraph; + + fn create_node_filter(self, graph: G) -> Result, GraphError> { + self.create_filter(graph) + } +} + impl Base for NodePropertyFilteredGraph { type Base = G; diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index dc87fe18f5..5c3c5beb98 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::NO_FILTER, Degree, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, + NodeTypeFilter, + }, + LazyNodeState, NodeStateOps, + }, view::{ internal::{ DynOrMutableGraph, DynamicGraph, IntoDynHop, IntoDynamic, IntoDynamicOrMutable, @@ -19,6 +26,7 @@ use crate::{ node::NodeView, nodes::Nodes, path::{PathFromGraph, PathFromNode}, + views::filter::model::AndFilter, }, }, errors::GraphError, @@ -37,7 +45,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 +452,34 @@ 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, DynNodeFilter>, } -impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph> { +impl<'py> FromPyObject<'py> for Nodes<'static, 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.node_select.is_filtered() { + Err(PyTypeError::new_err("Expected unfiltered nodes")) + } else { + Ok(Nodes::new_filtered( + nodes.base_graph().clone(), + NO_FILTER, + nodes.nodes.clone(), + )) + } + } +} + impl_nodeviewops!( PyNodes, nodes, - Nodes<'static, DynamicGraph, DynamicGraph>, + Nodes<'static, DynamicGraph, DynNodeFilter>, "Nodes", "NestedEdges", "PathFromGraph" @@ -486,20 +513,20 @@ impl PyNodes { } } -impl +impl> From> for PyNodes { fn from(value: Nodes<'static, G, GH>) -> Self { - let graph = value.iter_graph.into_dynamic(); + let select = value.node_select.into_dynamic(); let base_graph = value.graph.into_dynamic(); Self { - nodes: Nodes::new_filtered(base_graph, graph, value.nodes, value.node_types_filter), + nodes: Nodes::new_filtered(base_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: IntoDynNodeOp> IntoPyObject<'py> + for Nodes<'static, G, GH> { type Target = PyNodes; type Output = Bound<'py, Self::Target>; @@ -550,7 +577,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, DynNodeFilter> { self.nodes.id() } @@ -559,7 +586,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, DynNodeFilter> { self.nodes.name() } @@ -570,7 +597,7 @@ impl PyNodes { #[getter] fn earliest_time( &self, - ) -> LazyNodeState<'static, ops::EarliestTime, DynamicGraph, DynamicGraph> { + ) -> LazyNodeState<'static, ops::EarliestTime, DynamicGraph, DynNodeFilter> { self.nodes.earliest_time() } @@ -585,6 +612,7 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynNodeFilter, > { self.nodes.earliest_date_time() } @@ -594,7 +622,9 @@ 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, DynNodeFilter> { self.nodes.latest_time() } @@ -609,6 +639,7 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynNodeFilter, > { self.nodes.latest_date_time() } @@ -618,7 +649,9 @@ 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, DynNodeFilter> { self.nodes.history() } @@ -628,7 +661,8 @@ 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, DynNodeFilter> + { self.nodes.edge_history_count() } @@ -637,7 +671,7 @@ 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, DynNodeFilter> { self.nodes.node_type() } @@ -652,6 +686,7 @@ impl PyNodes { 'static, ops::Map, Option>>>, DynamicGraph, + DynNodeFilter, > { self.nodes.history_date_time() } @@ -680,7 +715,9 @@ impl PyNodes { /// /// Returns: /// DegreeView: a view of the undirected node degrees - fn degree(&self) -> LazyNodeState<'static, ops::Degree, DynamicGraph> { + fn degree( + &self, + ) -> LazyNodeState<'static, ops::Degree, DynamicGraph, DynNodeFilter> { self.nodes.degree() } @@ -688,7 +725,9 @@ 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, ops::Degree, DynamicGraph, DynNodeFilter> { self.nodes.in_degree() } @@ -696,7 +735,9 @@ 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, ops::Degree, DynamicGraph, DynNodeFilter> { self.nodes.out_degree() } @@ -780,12 +821,15 @@ 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, AndFilter> { 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: NodeFilterOp + 'graph> Repr for Nodes<'static, G, GH> { 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..0615bb1c5a 100644 --- a/raphtory/src/python/graph/node_state/node_state.rs +++ b/raphtory/src/python/graph/node_state/node_state.rs @@ -4,7 +4,9 @@ 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}, @@ -14,6 +16,7 @@ use crate::{ prelude::*, py_borrowing_iter, python::{ + graph::node_state::node_state::ops::NodeFilterOp, types::{repr::Repr, wrappers::iterators::PyBorrowingIterator}, utils::PyNodeRef, }, @@ -46,8 +49,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, DynNodeFilter> { + self.inner.nodes().into_dyn() } fn __eq__<'py>( @@ -317,11 +320,11 @@ 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, DynNodeFilter>, } impl $name { - pub fn inner(&self) -> &LazyNodeState<'static, $op, DynamicGraph, DynamicGraph> { + pub fn inner(&self) -> &LazyNodeState<'static, $op, DynamicGraph, DynNodeFilter> { &self.inner } } @@ -332,9 +335,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 +351,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, 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, 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, F> { type Target = $name; type Output = Bound<'py, Self::Target>; @@ -374,7 +379,7 @@ 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, DynNodeFilter> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { Ok(ob.downcast::<$name>()?.get().inner().clone()) } @@ -386,11 +391,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 +403,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 +425,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/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs index 96d8a72333..4b624cda44 100644 --- a/raphtory/src/python/packages/algorithms.rs +++ b/raphtory/src/python/packages/algorithms.rs @@ -59,7 +59,10 @@ use crate::{ }, db::{ api::{ - state::{Index, NodeState}, + state::{ + ops::{filter::NO_FILTER, DynNodeFilter, DynNodeOp}, + Index, NodeState, + }, view::internal::DynamicGraph, }, graph::{node::NodeView, nodes::Nodes}, @@ -777,7 +780,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(), NO_FILTER, index) } /// Simulate an SEIR dynamic on the network diff --git a/raphtory/src/python/types/repr.rs b/raphtory/src/python/types/repr.rs index ab4ca1fe47..99fb7a2340 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,7 +272,7 @@ impl Repr for &R { } } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>, Op: NodeOp + 'graph> Repr +impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'graph, Op: NodeOp + 'graph> Repr for LazyNodeState<'graph, Op, G, GH> where Op::Output: Repr + Send + Sync + 'graph, @@ -284,12 +284,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") From 908be839af0eb1b40a2f08eafae65dc535123cdb Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:41:02 +0000 Subject: [PATCH 30/42] finish ref, fix tests --- raphtory-graphql/graphs/g/.raph | 1 + raphtory-graphql/graphs/g/graph | Bin 0 -> 1678 bytes raphtory-graphql/graphs/graph/.raph | 1 + raphtory-graphql/graphs/graph/graph | Bin 0 -> 32 bytes .../graphs/path/to/event_graph/.raph | 1 + .../graphs/path/to/event_graph/graph | Bin 0 -> 2583 bytes .../graphs/test/first/internal/graph/.raph | 1 + .../graphs/test/first/internal/graph/graph | Bin 0 -> 32 bytes raphtory-graphql/graphs/test/graph/.raph | 1 + raphtory-graphql/graphs/test/graph/graph | Bin 0 -> 32 bytes .../graphs/test/second/internal/graph1/.raph | 1 + .../graphs/test/second/internal/graph1/graph | Bin 0 -> 32 bytes .../graphs/test/second/internal/graph2/.raph | 1 + .../graphs/test/second/internal/graph2/graph | Bin 0 -> 32 bytes raphtory-graphql/src/lib.rs | 29 ++- raphtory-graphql/src/model/graph/edges.rs | 2 +- raphtory-graphql/src/model/graph/filtering.rs | 10 +- raphtory-graphql/src/model/graph/graph.rs | 3 +- raphtory-graphql/src/model/graph/nodes.rs | 17 +- raphtory-graphql/src/model/graph/windowset.rs | 11 +- .../algorithms/components/in_components.rs | 1 + .../algorithms/components/out_components.rs | 1 + raphtory/src/algorithms/pathing/dijkstra.rs | 3 +- .../pathing/single_source_shortest_path.rs | 2 +- raphtory/src/db/api/state/group_by.rs | 18 +- raphtory/src/db/api/state/lazy_node_state.rs | 97 +++++--- raphtory/src/db/api/state/mod.rs | 2 +- raphtory/src/db/api/state/node_state.rs | 18 +- raphtory/src/db/api/state/node_state_ops.rs | 35 +-- .../src/db/api/state/node_state_ord_ops.rs | 56 ++--- raphtory/src/db/api/state/ops/history.rs | 1 - raphtory/src/db/api/state/ops/node.rs | 72 +++--- raphtory/src/db/api/view/filter_ops.rs | 8 +- raphtory/src/db/api/view/graph.rs | 6 +- .../src/db/api/view/internal/base_filter.rs | 10 +- .../src/db/api/view/internal/into_dynamic.rs | 6 +- raphtory/src/db/api/view/layer.rs | 6 +- raphtory/src/db/api/view/time.rs | 10 +- raphtory/src/db/graph/edge.rs | 8 +- raphtory/src/db/graph/edges.rs | 40 ++-- raphtory/src/db/graph/graph.rs | 20 +- raphtory/src/db/graph/node.rs | 8 +- raphtory/src/db/graph/nodes.rs | 226 ++++++++++-------- raphtory/src/db/graph/path.rs | 16 +- .../graph/views/filter/and_filtered_graph.rs | 27 ++- .../views/filter/edge_node_filtered_graph.rs | 7 +- .../filter/edge_property_filtered_graph.rs | 21 +- .../filter/exploded_edge_property_filter.rs | 19 +- .../src/db/graph/views/filter/internal.rs | 48 +++- raphtory/src/db/graph/views/filter/mod.rs | 17 +- .../db/graph/views/filter/model/and_filter.rs | 6 + .../graph/views/filter/model/edge_filter.rs | 66 ++++- .../filter/model/exploded_edge_filter.rs | 55 ++++- .../src/db/graph/views/filter/model/mod.rs | 18 +- .../graph/views/filter/model/node_filter.rs | 130 +++++++++- .../db/graph/views/filter/model/not_filter.rs | 3 + .../db/graph/views/filter/model/or_filter.rs | 6 + .../views/filter/model/property_filter.rs | 5 +- .../graph/views/filter/node_filtered_graph.rs | 65 +++++ .../views/filter/node_id_filtered_graph.rs | 41 ++-- .../views/filter/node_name_filtered_graph.rs | 34 +-- .../filter/node_property_filtered_graph.rs | 29 ++- .../views/filter/node_type_filtered_graph.rs | 34 +-- .../graph/views/filter/not_filtered_graph.rs | 18 +- .../graph/views/filter/or_filtered_graph.rs | 27 ++- raphtory/src/db/task/edge/eval_edge.rs | 9 +- raphtory/src/db/task/edge/eval_edges.rs | 8 +- raphtory/src/db/task/node/eval_node.rs | 14 +- raphtory/src/errors.rs | 3 + raphtory/src/python/filter/create_filter.rs | 26 +- .../src/python/filter/edge_filter_builders.rs | 2 +- .../filter/exploded_edge_filter_builder.rs | 8 +- raphtory/src/python/filter/filter_expr.rs | 20 +- .../src/python/filter/node_filter_builders.rs | 4 +- .../python/filter/property_filter_builders.rs | 116 +++++---- raphtory/src/python/graph/edges.rs | 4 +- raphtory/src/python/graph/node.rs | 95 +++++--- .../src/python/graph/node_state/node_state.rs | 25 +- raphtory/src/python/graph/views/graph_view.rs | 2 +- raphtory/src/python/packages/algorithms.rs | 7 +- .../types/macros/trait_impl/filter_ops.rs | 3 +- raphtory/src/python/types/repr.rs | 9 +- raphtory/src/search/edge_filter_executor.rs | 3 +- raphtory/src/search/mod.rs | 7 +- raphtory/src/search/node_filter_executor.rs | 20 +- 85 files changed, 1217 insertions(+), 593 deletions(-) create mode 100644 raphtory-graphql/graphs/g/.raph create mode 100644 raphtory-graphql/graphs/g/graph create mode 100644 raphtory-graphql/graphs/graph/.raph create mode 100644 raphtory-graphql/graphs/graph/graph create mode 100644 raphtory-graphql/graphs/path/to/event_graph/.raph create mode 100644 raphtory-graphql/graphs/path/to/event_graph/graph create mode 100644 raphtory-graphql/graphs/test/first/internal/graph/.raph create mode 100644 raphtory-graphql/graphs/test/first/internal/graph/graph create mode 100644 raphtory-graphql/graphs/test/graph/.raph create mode 100644 raphtory-graphql/graphs/test/graph/graph create mode 100644 raphtory-graphql/graphs/test/second/internal/graph1/.raph create mode 100644 raphtory-graphql/graphs/test/second/internal/graph1/graph create mode 100644 raphtory-graphql/graphs/test/second/internal/graph2/.raph create mode 100644 raphtory-graphql/graphs/test/second/internal/graph2/graph create mode 100644 raphtory/src/db/graph/views/filter/node_filtered_graph.rs diff --git a/raphtory-graphql/graphs/g/.raph b/raphtory-graphql/graphs/g/.raph new file mode 100644 index 0000000000..975d616f5a --- /dev/null +++ b/raphtory-graphql/graphs/g/.raph @@ -0,0 +1 @@ +{"node_count":15,"edge_count":15,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/g/graph b/raphtory-graphql/graphs/g/graph new file mode 100644 index 0000000000000000000000000000000000000000..eabae6fc31366693c18816def9266006b58d46f8 GIT binary patch literal 1678 zcmY*Y$#T>%5RGM9mSx!yX6rbt!!~hPGLwX5J|Z8$g`x^LaR3T_AYabwmg4HUOsn-4 zwWT*>`0Ty=^6mSNum67j9mBZSP2QVdn^onFtHvLpcD-%~C116ipbsge4L+|>B;YI8 zT@2KrhS*a;?c+fGs&WGjvzZQNKe}k=YBXyzYQ3w7I)?q z%R<#DmK9b5R#2=4H{45)@59WEupHiD$h$nV%XbmS0e5WZaK>BVb`FHiJ5ksr(&${P zZq5U{l`&RxKQ3x!d*$5$y}h9PtsUmQqr5xC4q)HiV+XMBDRxlo9_;%In@{dD>PG7Z zpd`}|c{l7l?)gaEkPdVWP0|&!-SJBuN#$L)wNp;ZE~Vu-GY9D#dXRnGj#F;wcup%p zwvr$)IYAGzlJVRFM_zz)mL4fQ^?r*~$@z)Jsd}u==UJbq^Apbg8G%2N27gZ4Q>Emj zt<-r5XJtm9MEaoQ^gUBbPTzB-OgJku0wod&B`5NQQgR|+N^j(j63)tuK#7z>$tiuM zn5pO6aQQXjF6DK-ygTbSuP34_utP$4hE6$f*5B?qaMs@`hs0oqgs?+U4xIn9#esQghk1SG@h<-p KHu42S+WimOy@DJ7 literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/graph/.raph b/raphtory-graphql/graphs/graph/.raph new file mode 100644 index 0000000000..eb24503c48 --- /dev/null +++ b/raphtory-graphql/graphs/graph/.raph @@ -0,0 +1 @@ +{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/graph/graph b/raphtory-graphql/graphs/graph/graph new file mode 100644 index 0000000000000000000000000000000000000000..4190f578f2e906b70834ccc29bef06c011347286 GIT binary patch literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/path/to/event_graph/.raph b/raphtory-graphql/graphs/path/to/event_graph/.raph new file mode 100644 index 0000000000..a5ce244763 --- /dev/null +++ b/raphtory-graphql/graphs/path/to/event_graph/.raph @@ -0,0 +1 @@ +{"node_count":3,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/path/to/event_graph/graph b/raphtory-graphql/graphs/path/to/event_graph/graph new file mode 100644 index 0000000000000000000000000000000000000000..51853dfccbdcc0e8b4468a19e0637641a253523e GIT binary patch literal 2583 zcmc&#O>fgc5RKO#*-R)b)1);njiQP}wNg>yHtEMHDkp9n;L=Ov(6o(|FQq9GoT%XE z@gtD9@FP%RcAcb7v7M#|u(Yx@wr6(Ud-E0_Apmclw$FmA?pQJ}gB$W0tPV$mVY44} zKD3*s!MHu{^x9htGFX(Wkk>CS$D>aFTo^jMCJO*M9E?W6N5Qr%U>tB=7GXulr&)xl ze<(-SZ>YW{geNZ@3$(U5y^fqF9r-oGbYuVLY6o~@5BYRZO~%0Oj<{rD}d69XBzS=!)qp( zYfP}nOt4v(J;LDM?Ox`I!7 zM7c;{sba0~&TWnGjcWI<>h3GEbPAW1TY_lQRJerP;NLJ`s{U#C5@sn8jU{xtU!Tp=0$19fx{`9EjI@rGEB4{BH zT}!IFayrf6h7kkt$>yL_u0@%T@U RvQVAr`X=C{b8K;9;}3HPFsc9m literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/test/first/internal/graph/.raph b/raphtory-graphql/graphs/test/first/internal/graph/.raph new file mode 100644 index 0000000000..eb24503c48 --- /dev/null +++ b/raphtory-graphql/graphs/test/first/internal/graph/.raph @@ -0,0 +1 @@ +{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/first/internal/graph/graph b/raphtory-graphql/graphs/test/first/internal/graph/graph new file mode 100644 index 0000000000000000000000000000000000000000..4190f578f2e906b70834ccc29bef06c011347286 GIT binary patch literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/test/graph/.raph b/raphtory-graphql/graphs/test/graph/.raph new file mode 100644 index 0000000000..eb24503c48 --- /dev/null +++ b/raphtory-graphql/graphs/test/graph/.raph @@ -0,0 +1 @@ +{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/graph/graph b/raphtory-graphql/graphs/test/graph/graph new file mode 100644 index 0000000000000000000000000000000000000000..4190f578f2e906b70834ccc29bef06c011347286 GIT binary patch literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/test/second/internal/graph1/.raph b/raphtory-graphql/graphs/test/second/internal/graph1/.raph new file mode 100644 index 0000000000..eb24503c48 --- /dev/null +++ b/raphtory-graphql/graphs/test/second/internal/graph1/.raph @@ -0,0 +1 @@ +{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/second/internal/graph1/graph b/raphtory-graphql/graphs/test/second/internal/graph1/graph new file mode 100644 index 0000000000000000000000000000000000000000..4190f578f2e906b70834ccc29bef06c011347286 GIT binary patch literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d literal 0 HcmV?d00001 diff --git a/raphtory-graphql/graphs/test/second/internal/graph2/.raph b/raphtory-graphql/graphs/test/second/internal/graph2/.raph new file mode 100644 index 0000000000..eb24503c48 --- /dev/null +++ b/raphtory-graphql/graphs/test/second/internal/graph2/.raph @@ -0,0 +1 @@ +{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/second/internal/graph2/graph b/raphtory-graphql/graphs/test/second/internal/graph2/graph new file mode 100644 index 0000000000000000000000000000000000000000..4190f578f2e906b70834ccc29bef06c011347286 GIT binary patch literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d literal 0 HcmV?d00001 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/edges.rs b/raphtory-graphql/src/model/graph/edges.rs index 84a2278d50..ca402db4e1 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::Filter, DynamicGraph, IterFilterOps}, graph::{edges::Edges, views::filter::model::edge_filter::CompositeEdgeFilter}, }, errors::GraphError, diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 7aea088f91..1d4ad8c964 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -873,11 +873,12 @@ fn build_windowed_property_filter_from_condition Result>, GraphError> { build_property_filter_from_condition_with_entity::>( prop_ref, cond, - Windowed::from_times(start, end), + Windowed::from_times(start, end, marker), ) } @@ -949,11 +950,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::( + let pf = build_windowed_property_filter_from_condition( prop_ref, &prop.where_, w.start, w.end, + NodeFilter, )?; return Ok(CompositeNodeFilter::PropertyWindowed(pf)); } @@ -1060,8 +1062,8 @@ impl TryFrom for CompositeEdgeFilter { 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, + let pf = build_windowed_property_filter_from_condition( + prop_ref, &p.where_, w.start, w.end, EdgeFilter, )?; return Ok(CompositeEdgeFilter::PropertyWindowed(pf)); } diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index 4e49e1e309..1cc6246899 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -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)) @@ -582,6 +582,7 @@ impl GqlGraph { let self_clone = self.clone(); blocking_compute(move || { let f: CompositeNodeFilter = filter.try_into()?; + println!("filter {}", f); let nodes = self_clone.graph.search_nodes(f, limit, offset)?; let result = nodes.into_iter().map(|vv| vv.into()).collect(); Ok(result) diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index 4efaf45bd3..d11e3b88ad 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, + state::{ops::DynNodeFilter, Index}, view::{BaseFilterOps, DynamicGraph, IterFilterOps}, }, - graph::{nodes::Nodes, views::filter::model::node_filter::CompositeNodeFilter}, + graph::{ + nodes::{IntoDynNodes, 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/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/src/algorithms/components/in_components.rs b/raphtory/src/algorithms/components/in_components.rs index e42183979f..1a17822042 100644 --- a/raphtory/src/algorithms/components/in_components.rs +++ b/raphtory/src/algorithms/components/in_components.rs @@ -74,6 +74,7 @@ where |_, _, _, local: Vec| { NodeState::new_from_eval_mapped(g.clone(), local, |v| { Nodes::new_filtered( + g.clone(), g.clone(), Const(true), Some(Index::from_iter(v.in_components)), diff --git a/raphtory/src/algorithms/components/out_components.rs b/raphtory/src/algorithms/components/out_components.rs index 8897ed72b2..0fb29a905a 100644 --- a/raphtory/src/algorithms/components/out_components.rs +++ b/raphtory/src/algorithms/components/out_components.rs @@ -74,6 +74,7 @@ where |_, _, _, local: Vec| { NodeState::new_from_eval_mapped(g.clone(), local, |v| { Nodes::new_filtered( + g.clone(), g.clone(), Const(true), Some(Index::from_iter(v.out_components)), diff --git a/raphtory/src/algorithms/pathing/dijkstra.rs b/raphtory/src/algorithms/pathing/dijkstra.rs index 6fdea09a3c..94ce147b10 100644 --- a/raphtory/src/algorithms/pathing/dijkstra.rs +++ b/raphtory/src/algorithms/pathing/dijkstra.rs @@ -189,7 +189,8 @@ pub fn dijkstra_single_source_shortest_paths, Vec<_>) = paths .into_iter() .map(|(id, (cost, path))| { - let nodes = Nodes::new_filtered(g.clone(), NO_FILTER, Some(Index::new(path))); + let nodes = + Nodes::new_filtered(g.clone(), g.clone(), NO_FILTER, Some(Index::new(path))); (id, (cost, nodes)) }) .unzip(); diff --git a/raphtory/src/algorithms/pathing/single_source_shortest_path.rs b/raphtory/src/algorithms/pathing/single_source_shortest_path.rs index 29e2bb6e92..66b2708b58 100644 --- a/raphtory/src/algorithms/pathing/single_source_shortest_path.rs +++ b/raphtory/src/algorithms/pathing/single_source_shortest_path.rs @@ -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(), NO_FILTER, Some(Index::from_iter(v))) + 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 2b667e5ef6..00b6aed178 100644 --- a/raphtory/src/db/api/state/group_by.rs +++ b/raphtory/src/db/api/state/group_by.rs @@ -37,7 +37,12 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr self.groups.iter().map(|(v, nodes)| { ( v, - Nodes::new_filtered(self.graph.clone(), Const(true), Some(nodes.clone())), + Nodes::new_filtered( + self.graph.clone(), + self.graph.clone(), + Const(true), + Some(nodes.clone()), + ), ) }) } @@ -78,7 +83,12 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr self.groups.get(index).map(|(v, nodes)| { ( v, - Nodes::new_filtered(self.graph.clone(), Const(true), Some(nodes.clone())), + Nodes::new_filtered( + self.graph.clone(), + self.graph.clone(), + Const(true), + Some(nodes.clone()), + ), ) }) } @@ -105,14 +115,14 @@ impl<'graph, V: Hash + Eq + Send + Sync + Clone, G: GraphViewOps<'graph>> NodeGr } pub trait NodeStateGroupBy<'graph>: NodeStateOps<'graph> { - fn groups(&self) -> NodeGroups; + fn groups(&self) -> NodeGroups; } impl<'graph, S: NodeStateOps<'graph>> NodeStateGroupBy<'graph> for S where S::OwnedValue: Hash + Eq + Debug, { - fn groups(&self) -> NodeGroups { + fn groups(&self) -> NodeGroups { self.group_by(|v| v.clone()) } } diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 2f92d025e0..fbe6ef73e8 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -3,15 +3,15 @@ use crate::{ db::{ api::{ state::{ - ops::{Const, DynNodeFilter, DynNodeOp, IntoDynNodeOp, NodeFilterOp, NodeOp}, + ops::{Const, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, NodeOp}, Index, NodeState, NodeStateOps, }, - view::{ - internal::{FilterOps, NodeList}, - BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, - }, + view::{internal::NodeList, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic}, + }, + graph::{ + node::NodeView, + nodes::{IntoDynNodes, Nodes}, }, - graph::{node::NodeView, nodes::Nodes}, }, prelude::*, }; @@ -23,8 +23,8 @@ use std::{ }; #[derive(Clone)] -pub struct LazyNodeState<'graph, Op, G, GH = Const> { - nodes: Nodes<'graph, G, GH>, +pub struct LazyNodeState<'graph, Op, G, GH, F = Const> { + nodes: Nodes<'graph, G, GH, F>, pub(crate) op: Op, } @@ -32,9 +32,10 @@ impl< 'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, - GH: NodeFilterOp + Clone + 'graph, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, RHS, - > PartialEq<&[RHS]> for LazyNodeState<'graph, O, G, GH> + > PartialEq<&[RHS]> for LazyNodeState<'graph, O, G, GH, F> where O::Output: PartialEq, { @@ -47,10 +48,11 @@ impl< 'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, - GH: NodeFilterOp + Clone + 'graph, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, RHS, const N: usize, - > PartialEq<[RHS; N]> for LazyNodeState<'graph, O, G, GH> + > PartialEq<[RHS; N]> for LazyNodeState<'graph, O, G, GH, F> where O::Output: PartialEq, { @@ -63,9 +65,10 @@ impl< 'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, - GH: NodeFilterOp + Clone + 'graph, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, RHS: NodeStateOps<'graph, OwnedValue = O::Output>, - > PartialEq for LazyNodeState<'graph, O, G, GH> + > PartialEq for LazyNodeState<'graph, O, G, GH, F> where O::Output: PartialEq, { @@ -84,9 +87,10 @@ impl< 'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, - GH: NodeFilterOp + Clone + 'graph, + GH: GraphViewOps<'graph>, + F: NodeFilterOp + Clone + 'graph, RHS, - > PartialEq> for LazyNodeState<'graph, O, G, GH> + > PartialEq> for LazyNodeState<'graph, O, G, GH, F> where O::Output: PartialEq, { @@ -95,8 +99,13 @@ where } } -impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'graph, O: NodeOp + 'graph> Debug - for LazyNodeState<'graph, O, 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 O::Output: Debug, { @@ -105,10 +114,15 @@ where } } -impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> - IntoIterator for LazyNodeState<'graph, O, 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>, O::Output); + type Item = (NodeView<'graph, GH>, O::Output); type IntoIter = BoxedLIter<'graph, Self::Item>; fn into_iter(self) -> Self::IntoIter { @@ -120,10 +134,10 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo } } -impl - LazyNodeState<'static, O, G, GH> +impl + LazyNodeState<'static, O, G, GH, F> { - pub fn into_dyn(self) -> LazyNodeState<'static, O, DynamicGraph, DynNodeFilter> { + pub fn into_dyn(self) -> LazyNodeState<'static, O, DynamicGraph, DynamicGraph, DynNodeFilter> { LazyNodeState { nodes: self.nodes.into_dyn(), op: self.op, @@ -131,10 +145,15 @@ impl } } -impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> - LazyNodeState<'graph, O, G, GH> +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>) -> Self { + pub(crate) fn new(op: O, nodes: Nodes<'graph, G, GH, F>) -> Self { Self { nodes, op } } @@ -146,7 +165,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo self.collect() } - pub fn compute(&self) -> NodeState<'graph, O::Output, G> { + pub fn compute(&self) -> NodeState<'graph, O::Output, GH> { if self.nodes.is_filtered() { let (keys, values): (IndexSet<_, ahash::RandomState>, Vec<_>) = self .par_iter() @@ -164,11 +183,17 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clo } } -impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'graph> - NodeStateOps<'graph> for LazyNodeState<'graph, O, 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 Select = GH; + type Select = F; type BaseGraph = G; + type Graph = GH; type Value<'a> = O::Output where @@ -176,7 +201,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'gr Self: 'a; type OwnedValue = O::Output; - fn graph(&self) -> &Self::BaseGraph { + fn graph(&self) -> &Self::Graph { &self.nodes.graph } @@ -216,7 +241,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'gr fn iter<'a>( &'a self, - ) -> impl Iterator, Self::Value<'a>)> + 'a + ) -> impl Iterator, Self::Value<'a>)> + 'a where 'graph: 'a, { @@ -226,13 +251,13 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'gr .map(move |node| (node, self.op.apply(&storage, node.node))) } - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Select> { + 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, { @@ -242,7 +267,7 @@ impl<'graph, O: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + 'gr .map(move |node| (node, self.op.apply(&storage, node.node))) } - 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<'_>)> { if self.nodes().is_list_filtered() { self.iter().nth(index) } else { 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 8294c0624d..22db08b750 100644 --- a/raphtory/src/db/api/state/node_state.rs +++ b/raphtory/src/db/api/state/node_state.rs @@ -303,6 +303,7 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState for NodeState<'graph, V, G> { type BaseGraph = G; + type Graph = G; type Select = Const; type Value<'a> = &'a V @@ -310,7 +311,7 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState 'graph: 'a; type OwnedValue = V; - fn graph(&self) -> &Self::BaseGraph { + fn graph(&self) -> &Self::Graph { &self.base_graph } @@ -340,7 +341,7 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState fn iter<'a>( &'a self, - ) -> impl Iterator, Self::Value<'a>)> + 'a + ) -> impl Iterator, Self::Value<'a>)> + 'a where 'graph: 'a, { @@ -359,15 +360,20 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState } } - fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Select> { - Nodes::new_filtered(self.base_graph.clone(), Const(true), self.keys.clone()) + fn nodes(&self) -> Nodes<'graph, Self::BaseGraph, Self::Graph, Self::Select> { + Nodes::new_filtered( + self.base_graph.clone(), + self.base_graph.clone(), + Const(true), + self.keys.clone(), + ) } fn par_iter<'a>( &'a self, ) -> impl ParallelIterator< Item = ( - NodeView<'a, &'a >::BaseGraph>, + NodeView<'a, &'a >::Graph>, >::Value<'a>, ), > @@ -390,7 +396,7 @@ impl<'graph, V: Clone + Send + Sync + 'graph, G: GraphViewOps<'graph>> NodeState } } - 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| { ( diff --git a/raphtory/src/db/api/state/node_state_ops.rs b/raphtory/src/db/api/state/node_state_ops.rs index 1046d0053f..26d3603bee 100644 --- a/raphtory/src/db/api/state/node_state_ops.rs +++ b/raphtory/src/db/api/state/node_state_ops.rs @@ -15,9 +15,10 @@ 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 @@ -26,7 +27,7 @@ pub trait NodeStateOps<'graph>: type OwnedValue: Clone + Send + Sync + 'graph; - fn graph(&self) -> &Self::BaseGraph; + fn graph(&self) -> &Self::Graph; fn iter_values<'a>(&'a self) -> impl Iterator> + 'a where @@ -41,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::Select>; + 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>; @@ -61,14 +62,14 @@ 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> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { let mut state: Vec<_> = self .par_iter() .map(|(n, v)| (n.node, v.borrow().clone())) @@ -101,12 +102,12 @@ pub trait NodeStateOps<'graph>: >( &self, cmp: F, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { + ) -> 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> { + fn sort_by_id(&self) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { self.sort_by(|(n1, _), (n2, _)| n1.id().cmp(&n2.id())) } @@ -128,7 +129,7 @@ pub trait NodeStateOps<'graph>: &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { + ) -> NodeState<'graph, Self::OwnedValue, Self::Graph> { let values = node_state_ord_ops::top_k( self.iter(), |(_, v1), (_, v2)| cmp(v1.borrow(), v2.borrow()), @@ -146,14 +147,14 @@ pub trait NodeStateOps<'graph>: &self, cmp: F, k: usize, - ) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { + ) -> 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())) } @@ -161,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())) } @@ -169,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 { @@ -186,7 +187,7 @@ pub trait NodeStateOps<'graph>: >( &self, group_fn: F, - ) -> NodeGroups { + ) -> NodeGroups { NodeGroups::new( self.par_iter() .map(|(node, v)| (node.node, group_fn(v.borrow()))), 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 766ec2440e..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,8 +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>; + 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. /// @@ -75,26 +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>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; - fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; + 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) @@ -111,8 +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>; + 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. /// @@ -128,26 +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>; + fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::Graph>; - fn bottom_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph>; + 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) @@ -158,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> { + 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 { @@ -169,23 +164,23 @@ where } } - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { + 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> { + 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) } } @@ -194,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> { + 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 { @@ -205,23 +197,23 @@ where } } - fn top_k(&self, k: usize) -> NodeState<'graph, Self::OwnedValue, Self::BaseGraph> { + 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> { + 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/history.rs b/raphtory/src/db/api/state/ops/history.rs index 165318c79f..61c8254d0d 100644 --- a/raphtory/src/db/api/state/ops/history.rs +++ b/raphtory/src/db/api/state/ops/history.rs @@ -5,7 +5,6 @@ use crate::db::api::{ use itertools::Itertools; use raphtory_api::core::entities::VID; use raphtory_storage::graph::graph::GraphStorage; -use std::sync::Arc; #[derive(Debug, Clone)] pub struct EarliestTime { diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index a695210a60..1c4c1cd167 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,18 +1,15 @@ -use crate::{ - db::{ - api::{ - state::ops::filter::{Mask, MaskOp}, - view::internal::{ - time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, - GraphView, - }, - }, - graph::{ - create_node_type_filter, - views::filter::model::{not_filter::NotFilter, or_filter::OrFilter, AndFilter}, +use crate::db::{ + api::{ + state::ops::filter::{Mask, MaskOp}, + view::internal::{ + time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, + GraphView, }, }, - prelude::GraphViewOps, + graph::{ + create_node_type_filter, + views::filter::model::{and_filter::AndOp, not_filter::NotOp, or_filter::OrOp}, + }, }; use raphtory_api::core::{ entities::{GID, VID}, @@ -28,11 +25,22 @@ use std::{ops::Deref, sync::Arc}; pub trait NodeFilterOp: NodeOp + Clone { fn is_filtered(&self) -> bool; - fn and(self, other: T) -> AndFilter; + fn and(self, other: T) -> AndOp; + + fn or(self, other: T) -> OrOp; + + fn not(self) -> NotOp; +} + +#[derive(Clone)] +pub struct NotANodeFilter; - fn or(self, other: T) -> OrFilter; +impl NodeOp for NotANodeFilter { + type Output = bool; - fn not(self) -> NotFilter; + fn apply(&self, _storage: &GraphStorage, _node: VID) -> Self::Output { + panic!("Not a node filter") + } } impl + Clone> NodeFilterOp for Op { @@ -41,22 +49,22 @@ impl + Clone> NodeFilterOp for Op { self.const_value().is_none_or(|v| !v) } - fn and(self, other: T) -> AndFilter { - AndFilter { + fn and(self, other: T) -> AndOp { + AndOp { left: self, right: other, } } - fn or(self, other: T) -> OrFilter { - OrFilter { + fn or(self, other: T) -> OrOp { + OrOp { left: self, right: other, } } - fn not(self) -> NotFilter { - NotFilter { 0: self } + fn not(self) -> NotOp { + NotOp { 0: self } } } @@ -107,7 +115,7 @@ where impl IntoDynNodeOp for Eq where Eq: NodeOp + 'static {} -impl NodeOp for AndFilter +impl NodeOp for AndOp where L: NodeOp, R: NodeOp, @@ -119,9 +127,9 @@ where } } -impl IntoDynNodeOp for AndFilter where Self: NodeOp + 'static {} +impl IntoDynNodeOp for AndOp where Self: NodeOp + 'static {} -impl NodeOp for OrFilter +impl NodeOp for OrOp where L: NodeOp, R: NodeOp, @@ -133,9 +141,9 @@ where } } -impl IntoDynNodeOp for OrFilter where Self: NodeOp + 'static {} +impl IntoDynNodeOp for OrOp where Self: NodeOp + 'static {} -impl NodeOp for NotFilter +impl NodeOp for NotOp where T: NodeOp, { @@ -146,7 +154,7 @@ where } } -impl IntoDynNodeOp for NotFilter where Self: NodeOp + 'static {} +impl IntoDynNodeOp for NotOp where Self: NodeOp + 'static {} #[derive(Clone, Copy, Debug)] pub struct Const(pub V); @@ -240,7 +248,7 @@ impl NodeOp for Degree { impl IntoDynNodeOp for Degree {} -impl NodeOp for Arc> { +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) @@ -269,10 +277,10 @@ impl NodeOp for Map { impl IntoDynNodeOp for Map {} -pub type NodeTypeFilter = Mask; +pub type NodeTypeFilterOp = Mask; -impl NodeTypeFilter { - pub fn new, V: AsRef>( +impl NodeTypeFilterOp { + pub fn new_from_values, V: AsRef>( node_types: I, view: impl GraphView, ) -> Self { diff --git a/raphtory/src/db/api/view/filter_ops.rs b/raphtory/src/db/api/view/filter_ops.rs index fa21ee35d9..de5af7fc7d 100644 --- a/raphtory/src/db/api/view/filter_ops.rs +++ b/raphtory/src/db/api/view/filter_ops.rs @@ -1,16 +1,16 @@ use crate::{ db::{ - api::view::internal::{BaseFilter, IterFilter}, + api::view::internal::{Filter, IterFilter}, graph::views::filter::internal::CreateFilter, }, errors::GraphError, }; -pub trait BaseFilterOps<'graph>: BaseFilter<'graph> { +pub trait BaseFilterOps<'graph>: Filter<'graph> { fn filter( &self, filter: F, - ) -> Result>, GraphError> { + ) -> Result>, GraphError> { Ok(self.apply_filter(filter.create_filter(self.base_graph().clone())?)) } } @@ -24,5 +24,5 @@ pub trait IterFilterOps<'graph>: IterFilter<'graph> { } } -impl<'graph, T: BaseFilter<'graph>> BaseFilterOps<'graph> for T {} +impl<'graph, T: Filter<'graph>> BaseFilterOps<'graph> for T {} impl<'graph, T: IterFilter<'graph>> IterFilterOps<'graph> for T {} diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index e4512e2300..f81a35e481 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -922,14 +922,14 @@ pub trait StaticGraphViewOps: GraphView + 'static {} impl StaticGraphViewOps for G {} -impl<'graph, G> BaseFilter<'graph> for G +impl<'graph, G> Filter<'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/base_filter.rs index d65bfbab8c..3ecb2ee09d 100644 --- a/raphtory/src/db/api/view/internal/base_filter.rs +++ b/raphtory/src/db/api/view/internal/base_filter.rs @@ -1,14 +1,14 @@ use crate::prelude::GraphViewOps; -pub trait BaseFilter<'graph> { - type BaseGraph: GraphViewOps<'graph> + 'graph; +pub trait Filter<'graph> { + type Graph: GraphViewOps<'graph> + 'graph; - type Filtered + 'graph>: BaseFilter< + type Filtered + 'graph>: Filter< 'graph, - BaseGraph = FilteredGraph, + Graph = FilteredGraph, >; - fn base_graph(&self) -> &Self::BaseGraph; + fn base_graph(&self) -> &Self::Graph; fn apply_filter + 'graph>( &self, diff --git a/raphtory/src/db/api/view/internal/into_dynamic.rs b/raphtory/src/db/api/view/internal/into_dynamic.rs index da7bb22d08..7a00be3029 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, Filter, 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: Filter<'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/layer.rs b/raphtory/src/db/api/view/layer.rs index ca5c71dcee..f24f63e9b2 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::{Filter, 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: Filter<'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/time.rs b/raphtory/src/db/api/view/time.rs index 7de56237e7..e2a8903893 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::{Filter, GraphTimeSemanticsOps, 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::Filter, 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: Filter<'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: Filter<'graph> + 'graph + InternalTimeOps<'graph>> TimeOps<'graph> for V { type WindowedViewType = V::InternalWindowedView; fn start(&self) -> Option { diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs index 349f879918..b3327aa1cf 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, Filter, GraphView, 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> Filter<'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..1e4c6b4a36 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::{Filter, FilterOps, IterFilter, 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> Filter<'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 } @@ -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> Filter<'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,7 +349,7 @@ 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, } @@ -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 7f903007fa..7266f6f2f4 100644 --- a/raphtory/src/db/graph/graph.rs +++ b/raphtory/src/db/graph/graph.rs @@ -232,12 +232,14 @@ pub fn assert_node_equal_layer<'graph, G1: GraphViewOps<'graph>, G2: GraphViewOp pub fn assert_nodes_equal< 'graph, G1: GraphViewOps<'graph>, - GH1: NodeFilterOp + 'graph, + GH1: GraphViewOps<'graph>, + F1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, - GH2: NodeFilterOp + '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,12 +247,14 @@ pub fn assert_nodes_equal< pub fn assert_nodes_equal_layer< 'graph, G1: GraphViewOps<'graph>, - GH1: NodeFilterOp + 'graph, + GH1: GraphViewOps<'graph>, + F1: NodeFilterOp + 'graph, G2: GraphViewOps<'graph>, - GH2: NodeFilterOp + '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..cdf0cdd822 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::{Filter, GraphTimeSemanticsOps, 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> Filter<'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 eca05ac130..1b7599ac0c 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -3,11 +3,11 @@ use crate::{ db::{ api::{ state::{ - ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp, NodeTypeFilter}, + ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp, NodeTypeFilterOp}, Index, LazyNodeState, }, view::{ - internal::{BaseFilter, FilterOps, IterFilter, NodeList, Static}, + internal::{Filter, FilterOps, NodeList, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, }, }, @@ -15,17 +15,13 @@ use crate::{ edges::NestedEdges, node::NodeView, path::PathFromGraph, - views::filter::{internal::CreateNodeFilter, model::AndFilter}, + views::filter::{internal::CreateFilter, model::and_filter::AndOp}, }, }, errors::GraphError, prelude::*, }; -use raphtory_api::inherit::Base; -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, @@ -36,9 +32,10 @@ use std::{ }; #[derive(Clone)] -pub struct Nodes<'graph, G, F = Const> { - pub(crate) graph: G, - pub(crate) node_select: F, +pub struct Nodes<'graph, G, GH = G, F = Const> { + pub(crate) base_graph: G, + pub(crate) graph: GH, + pub(crate) selector: F, pub(crate) nodes: Option>, _marker: PhantomData<&'graph ()>, } @@ -46,18 +43,24 @@ pub struct Nodes<'graph, G, F = Const> { impl< 'graph, G: GraphViewOps<'graph>, - GH: NodeFilterOp + Clone + '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: NodeFilterOp + Clone + '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() @@ -65,16 +68,24 @@ impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph, V: AsNo } } -impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + '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: NodeFilterOp + Clone + '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.graph, &other.graph) { @@ -87,31 +98,37 @@ impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + Clone + 'graph> Partial } } -impl Nodes<'static, G, GH> { - pub fn into_dyn(self) -> Nodes<'static, DynamicGraph, Arc>> { +pub trait IntoDynNodes { + fn into_dyn(self) + -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>>; +} + +impl IntoDynNodes + for Nodes<'static, G, GH, F> +{ + fn into_dyn( + self, + ) -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>> { Nodes { + base_graph: self.base_graph.into_dynamic(), graph: self.graph.into_dynamic(), - node_select: self.node_select.into_dynamic(), + selector: self.selector.into_dynamic(), nodes: self.nodes, _marker: Default::default(), } } } -// impl> From> -// for Nodes<'static, DynamicGraph, Arc>> -// where -// G: GraphViewOps<'graph> + IntoDynamic + Static, -// { -// fn from(value: Nodes<'graph, G, GH>) -> Self { -// Nodes { -// base_graph: value.base_graph.into_dynamic(), -// node_select: value.node_select.into_dynamic(), -// nodes: value.nodes, -// _marker: PhantomData, -// } -// } -// } +impl + From> + for Nodes<'static, DynamicGraph, DynamicGraph, Arc>> +{ + fn from( + value: Nodes<'static, G, GH, F>, + ) -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>> { + value.into_dyn() + } +} impl<'graph, G> Nodes<'graph, G> where @@ -119,23 +136,26 @@ where { pub fn new(graph: G) -> Self { Self { + base_graph: graph.clone(), graph: graph.clone(), - node_select: Const(true), + selector: Const(true), nodes: 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: NodeFilterOp + Clone + 'graph, + GH: GraphViewOps<'graph> + 'graph, + F: NodeFilterOp + Clone + 'graph, { - pub fn new_filtered(base_graph: G, node_select: GH, nodes: Option>) -> Self { + pub fn new_filtered(base_graph: G, graph: GH, selector: F, nodes: Option>) -> Self { Self { - graph: base_graph, - node_select, + base_graph, + graph, + selector, nodes, _marker: PhantomData, } @@ -148,49 +168,56 @@ where } } + pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH, F> { + Nodes::new_filtered( + self.base_graph.clone(), + self.graph.clone(), + self.selector.clone(), + Some(index), + ) + } + pub(crate) fn par_iter_refs(&self) -> impl ParallelIterator + 'graph { - let g = self.graph.core_graph().lock(); - let view = self.graph.clone(); - let node_select = self.node_select.clone(); + 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) }) } - pub fn indexed(&self, index: Index) -> Nodes<'graph, G, GH> { - Nodes::new_filtered(self.graph.clone(), self.node_select.clone(), Some(index)) - } - #[inline] pub(crate) fn iter_refs(&self) -> impl Iterator + Send + Sync + 'graph { - let g = self.graph.core_graph().lock(); - let view = self.graph.clone(); - let node_select = self.node_select.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); - view.filter_node(node.as_ref()) && node_select.apply(&g, vid) + 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.graph, v)) } - pub fn iter_owned(&self) -> BoxedLIter<'graph, NodeView<'graph, G>> { - let base_graph = self.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.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.graph.clone(), n)) } @@ -221,7 +248,7 @@ where self.iter().next().is_none() } - pub fn get(&self, node: V) -> Option> { + 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.graph.clone(), vid)) @@ -230,12 +257,13 @@ where pub fn type_filter, V: AsRef>( &self, node_types: I, - ) -> Nodes<'graph, G, AndFilter> { - let node_types_filter = NodeTypeFilter::new(node_types, &self.graph); - let node_select = self.node_select.clone().and(node_types_filter); + ) -> 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(), graph: self.graph.clone(), - node_select, + selector: node_select, nodes: self.nodes.clone(), _marker: PhantomData, } @@ -244,7 +272,7 @@ 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.graph.node(n).map(|n| n.node)) @@ -252,7 +280,7 @@ where self.indexed(index) } - pub fn collect(&self) -> Vec> { + pub fn collect(&self) -> Vec> { self.iter_owned().collect() } @@ -265,67 +293,70 @@ where } pub fn is_list_filtered(&self) -> bool { - !self.graph.node_list_trusted() || self.node_select.is_filtered() + !self.graph.node_list_trusted() || self.selector.is_filtered() } pub fn is_filtered(&self) -> bool { - self.graph.filtered() || self.node_select.is_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.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( + pub fn select( &self, filter: Filter, - ) -> Result>>, GraphError> { - let node_select = self - .node_select + ) -> Result>>, GraphError> { + let selector = self + .selector .clone() - .and(filter.create_node_filter(self.graph.clone())?); + .and(filter.create_node_filter(self.base_graph.clone())?); Ok(Nodes { + base_graph: self.base_graph.clone(), graph: self.graph.clone(), - node_select, + 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: NodeFilterOp + '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.graph } - fn map(&self, op: F) -> Self::ValueType { + 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.graph.clone(); let nodes = self.clone(); @@ -335,7 +366,7 @@ where op(cg, &graph, node).into_dyn_boxed() }); NestedEdges { - base_graph: self.graph.clone(), + graph: self.graph.clone(), nodes, edges, } @@ -343,10 +374,10 @@ 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.graph.clone(); let nodes = self.clone(); @@ -358,15 +389,16 @@ where } } -impl<'graph, Current, G> BaseFilter<'graph> for Nodes<'graph, Current, G> +impl<'graph, G, Current, F> Filter<'graph> for Nodes<'graph, G, Current, F> where + G: GraphViewOps<'graph> + 'graph, Current: GraphViewOps<'graph> + 'graph, - G: NodeFilterOp + Clone + 'graph, + F: NodeFilterOp + Clone + '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 { + fn base_graph(&self) -> &Self::Graph { &self.graph } @@ -375,20 +407,22 @@ where filtered_graph: Next, ) -> Self::Filtered { Nodes { + base_graph: self.base_graph.clone(), graph: filtered_graph, - node_select: self.node_select.clone(), + selector: self.selector.clone(), nodes: self.nodes.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: NodeFilterOp + '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..6a363fe68e 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::{Filter, FilterOps, IterFilter, 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> Filter<'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 } @@ -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> Filter<'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 } 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..0ba5365771 100644 --- a/raphtory/src/db/graph/views/filter/and_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/and_filtered_graph.rs @@ -2,13 +2,18 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::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}, + graph::views::filter::{ + internal::CreateFilter, + model::{and_filter::AndOp, AndFilter}, + }, }, errors::GraphError, prelude::GraphViewOps, @@ -39,6 +44,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 +63,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_node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs index 6395228e32..622377d8c6 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -3,16 +3,13 @@ use crate::{ api::{ properties::internal::InheritPropertiesOps, view::internal::{ - FilterOps, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, + Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, Static, }, }, - graph::views::filter::model::{ - edge_filter::Endpoint, node_filter::CompositeNodeFilter, - property_filter::PropertyFilterOps, - }, + graph::views::filter::model::edge_filter::Endpoint, }, prelude::GraphViewOps, }; 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 868150d497..992df3520f 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 { 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 9b7834707c..3c36c86ad8 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, @@ -65,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, @@ -84,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, @@ -96,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 { diff --git a/raphtory/src/db/graph/views/filter/internal.rs b/raphtory/src/db/graph/views/filter/internal.rs index 4e8f4c18d5..ac9a853fb9 100644 --- a/raphtory/src/db/graph/views/filter/internal.rs +++ b/raphtory/src/db/graph/views/filter/internal.rs @@ -1,5 +1,8 @@ use crate::{ - db::api::{state::ops::NodeFilterOp, view::internal::GraphView}, + db::{ + api::{state::ops::NodeFilterOp, view::internal::GraphView}, + graph::views::filter::node_filtered_graph::NodeFilteredGraph, + }, errors::GraphError, prelude::GraphViewOps, }; @@ -10,25 +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>; -} -pub trait CreateNodeFilter: Sized { - type NodeFilter: NodeFilterOp; - fn create_node_filter(self, graph: G) -> Result, GraphError>; + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError>; } -// Not sure if this is useful -impl CreateNodeFilter for T { - type NodeFilter = T; +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( + fn create_node_filter<'graph, G: GraphView + 'graph>( self, _graph: G, - ) -> Result, GraphError> { + ) -> 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 707edfa9d7..f716afcfb0 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1,11 +1,10 @@ -use crate::db::graph::views::filter::model::node_filter::{NodeNameFilter, NodeTypeFilter}; - pub mod and_filtered_graph; pub mod edge_node_filtered_graph; pub mod edge_property_filtered_graph; pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; +mod node_filtered_graph; mod node_id_filtered_graph; pub mod node_name_filtered_graph; pub mod node_property_filtered_graph; @@ -8268,13 +8267,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, 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 2696e0bb61..415146d91c 100644 --- a/raphtory/src/db/graph/views/filter/model/and_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/and_filter.rs @@ -7,6 +7,12 @@ use crate::{ }; use std::{fmt, fmt::Display}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AndOp { + pub(crate) left: L, + pub(crate) right: R, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AndFilter { pub(crate) left: L, 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 a0d44e4ca9..a70eb6ace2 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,6 +1,9 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, @@ -24,7 +27,7 @@ use crate::{ }; use raphtory_api::core::entities::{properties::prop::Prop, GID}; use raphtory_core::utils::time::IntoTime; -use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; +use std::{fmt, fmt::Display, sync::Arc}; #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Endpoint { @@ -59,6 +62,11 @@ 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, @@ -101,6 +109,13 @@ impl CreateFilter for CompositeEdgeFilter { } } } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl TryAsCompositeFilter for CompositeEdgeFilter { @@ -136,7 +151,7 @@ impl EdgeFilter { #[inline] pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + Windowed::from_times(start, end, EdgeFilter) } } @@ -266,6 +281,11 @@ where where T: 'graph; + type NodeFilter<'graph, G: GraphView + 'graph> + = NotANodeFilter + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -276,6 +296,16 @@ where let filter = self.try_as_composite_edge_filter()?; filter.create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> + where + Self: 'graph, + { + Err(GraphError::NotNodeFilter) + } } impl InternalPropertyFilterOps for EndpointWrapper { @@ -732,12 +762,27 @@ impl TryAsCompositeFilter for PropertyFilter> { impl CreateFilter for PropertyFilter> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type NodeFilter<'graph, G: GraphView + 'graph> + = NotANodeFilter + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { self.try_as_composite_edge_filter()?.create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> + where + Self: 'graph, + { + Err(GraphError::NotNodeFilter) + } } impl TryAsCompositeFilter for PropertyFilter>> { @@ -770,12 +815,27 @@ impl TryAsCompositeFilter for PropertyFilter>> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type NodeFilter<'graph, G: GraphView + 'graph> + = NotANodeFilter + where + Self: 'graph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { self.try_as_composite_edge_filter()?.create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> + where + Self: 'graph, + { + Err(GraphError::NotNodeFilter) + } } impl EntityMarker for EndpointWrapper where M: EntityMarker + Send + Sync + Clone + 'static {} 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 index f30aef0e63..2e092963ec 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -1,6 +1,9 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, @@ -60,6 +63,11 @@ impl Display for CompositeExplodedEdgeFilter { 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, @@ -102,6 +110,13 @@ impl CreateFilter for CompositeExplodedEdgeFilter { } } } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl TryAsCompositeFilter for CompositeExplodedEdgeFilter { @@ -247,6 +262,11 @@ where = Arc where T: 'graph; + type NodeFilter<'graph, G> + = NotANodeFilter + where + Self: 'graph, + G: GraphView + 'graph; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, @@ -258,6 +278,13 @@ where self.try_as_composite_exploded_edge_filter()? .create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl InternalPropertyFilterOps for ExplodedEndpointWrapper { @@ -712,7 +739,7 @@ impl ExplodedEdgeFilter { #[inline] pub fn window(start: S, end: E) -> Windowed { - Windowed::from_times(start, end) + Windowed::from_times(start, end, ExplodedEdgeFilter) } } @@ -745,6 +772,11 @@ impl TryAsCompositeFilter for PropertyFilter impl CreateFilter for PropertyFilter> { 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, @@ -753,6 +785,13 @@ impl CreateFilter for PropertyFilter> { self.try_as_composite_exploded_edge_filter()? .create_filter(graph) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl TryAsCompositeFilter for PropertyFilter>> { @@ -784,6 +823,11 @@ impl TryAsCompositeFilter for PropertyFilter>> { 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, @@ -792,6 +836,13 @@ impl CreateFilter for PropertyFilter( + self, + _graph: G, + ) -> Result, GraphError> { + Err(GraphError::NotNodeFilter) + } } impl EntityMarker for ExplodedEndpointWrapper where diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 6a8f01c316..6cb2f08d55 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -10,15 +10,13 @@ use crate::{ property_filter::{MetadataFilterBuilder, PropertyFilter, PropertyFilterBuilder}, }, errors::GraphError, - prelude::{GraphViewOps, NodeViewOps}, }; use raphtory_api::core::{ entities::{GidRef, GID}, storage::timeindex::TimeIndexEntry, }; use raphtory_core::utils::time::IntoTime; -use raphtory_storage::graph::edges::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; @@ -33,24 +31,20 @@ pub mod property_filter; pub struct Windowed { pub start: TimeIndexEntry, pub end: TimeIndexEntry, - pub _marker: PhantomData, + pub marker: M, } impl Windowed { #[inline] - pub fn new(start: TimeIndexEntry, end: TimeIndexEntry) -> Self { - Self { - start, - end, - _marker: PhantomData, - } + pub fn new(start: TimeIndexEntry, end: TimeIndexEntry, marker: M) -> Self { + Self { start, end, marker } } #[inline] - pub fn from_times(start: S, end: E) -> Self { + pub fn from_times(start: S, end: E, marker: M) -> Self { let s = TimeIndexEntry::start(start.into_time()); let e = TimeIndexEntry::end(end.into_time()); - Self::new(s, e) + Self::new(s, e, marker) } } 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 b1fb7cc856..04fc76c316 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,16 +1,23 @@ use crate::{ db::{ - api::view::BoxableGraphView, + api::{ + state::{ + ops::{filter::MaskOp, NodeTypeFilterOp, TypeId}, + NodeOp, + }, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ internal::CreateFilter, model::{ - edge_filter::{CompositeEdgeFilter, EndpointWrapper}, - exploded_edge_filter::CompositeExplodedEdgeFilter, - filter_operator::FilterOperator, - property_filter::PropertyFilter, - AndFilter, Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, - Windowed, + and_filter::AndOp, edge_filter::CompositeEdgeFilter, + exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, + not_filter::NotOp, or_filter::OrOp, property_filter::PropertyFilter, AndFilter, + Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, }, + node_id_filtered_graph::NodeIdFilteredGraph, + node_name_filtered_graph::NodeNameFilteredGraph, + node_type_filtered_graph::NodeTypeFilteredGraph, }, }, errors::GraphError, @@ -35,6 +42,27 @@ impl From for NodeIdFilter { } } +impl CreateFilter for NodeIdFilter { + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeIdFilteredGraph; + + type NodeFilter<'graph, G: GraphView + '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)) + } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.create_filter(graph) + } +} + #[derive(Debug, Clone)] pub struct NodeNameFilter(pub Filter); @@ -50,6 +78,26 @@ impl From for NodeNameFilter { } } +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)) + } + + type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilteredGraph; + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.create_filter(graph) + } +} + #[derive(Debug, Clone)] pub struct NodeTypeFilter(pub Filter); @@ -65,6 +113,41 @@ impl From for NodeTypeFilter { } } +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())) + } + + type NodeFilter<'graph, G: GraphView + 'graph> = NodeTypeFilterOp; + + 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), @@ -91,6 +174,8 @@ impl Display for CompositeNodeFilter { impl CreateFilter for CompositeNodeFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + type NodeFilter<'graph, G: GraphView + 'graph> = Arc + 'graph>; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -126,6 +211,35 @@ impl CreateFilter for CompositeNodeFilter { } } } + + 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_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_node_filter(graph)?)), + CompositeNodeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_node_filter(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) => { + Ok(Arc::new(NotOp(filter.clone().create_node_filter(graph)?))) + } + } + } } impl TryAsCompositeFilter for CompositeNodeFilter { @@ -336,7 +450,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> { 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 5fb66a434c..227dd3f609 100644 --- a/raphtory/src/db/graph/views/filter/model/not_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/not_filter.rs @@ -7,6 +7,9 @@ use crate::{ }; use std::{fmt, fmt::Display}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NotOp(pub(crate) T); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct NotFilter(pub(crate) T); 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 7cb08da838..8a75e17c07 100644 --- a/raphtory/src/db/graph/views/filter/model/or_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/or_filter.rs @@ -7,6 +7,12 @@ use crate::{ }; use std::{fmt, fmt::Display}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OrOp { + pub(crate) left: L, + pub(crate) right: R, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct OrFilter { pub(crate) left: L, 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 0923f6bd60..69f6bf1d91 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -32,10 +32,7 @@ use raphtory_api::core::{ }, EID, }, - storage::{ - arc_str::ArcStr, - timeindex::{AsTime, TimeIndexEntry}, - }, + storage::{arc_str::ArcStr, timeindex::TimeIndexEntry}, }; use raphtory_storage::graph::{ edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, 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..fd92b363a7 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs @@ -0,0 +1,65 @@ +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(crate) 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> InheritCoreGraphOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritStorageOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritLayerOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritListOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritMaterialize for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritAllEdgeFilterOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritPropertiesOps for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritTimeSemantics for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> InheritNodeHistoryFilter for NodeFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, F> 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 index fc73512aec..9d0c8afbc1 100644 --- a/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs @@ -2,27 +2,27 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::NodeOp, view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::{ - internal::CreateFilter, - model::{ - node_filter::{NodeFilter, NodeIdFilter}, - Filter, + GraphView, Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, InternalNodeFilterOps, Static, }, }, + graph::views::filter::model::Filter, }, - errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::{core::entities::LayerIds, inherit::Base}; +use raphtory_api::{ + core::entities::{LayerIds, VID}, + inherit::Base, +}; use raphtory_storage::{ core_ops::InheritCoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + graph::{ + graph::GraphStorage, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + }, }; #[derive(Debug, Clone)] @@ -37,15 +37,14 @@ impl NodeIdFilteredGraph { } } -impl CreateFilter for NodeIdFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeIdFilteredGraph; +impl NodeOp for NodeIdFilteredGraph { + type Output = bool; - 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)) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let layer_ids = self.graph.layer_ids(); + let nodes = storage.nodes(); + let node_ref = nodes.node(node); + self.internal_filter_node(node_ref, layer_ids) } } 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 index 6a60cc5b8f..112f3cf108 100644 --- a/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs @@ -2,21 +2,27 @@ 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::Filter, NodeNameFilter}, + graph::views::filter::model::Filter, }, - errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::{core::entities::LayerIds, inherit::Base}; +use raphtory_api::{ + core::entities::{LayerIds, VID}, + inherit::Base, +}; use raphtory_storage::{ core_ops::InheritCoreGraphOps, - graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + graph::{ + graph::GraphStorage, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + }, }; #[derive(Debug, Clone)] @@ -31,14 +37,14 @@ impl NodeNameFilteredGraph { } } -impl CreateFilter for NodeNameFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeNameFilteredGraph; +impl NodeOp for NodeNameFilteredGraph { + type Output = bool; - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - Ok(NodeNameFilteredGraph::new(graph, self.0)) + fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { + let layer_ids = self.graph.layer_ids(); + let nodes = storage.nodes(); + let node_ref = nodes.node(node); + self.internal_filter_node(node_ref, layer_ids) } } 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 f0a0f74611..f0e9ef3e7f 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 @@ -11,7 +11,7 @@ use crate::{ }, graph::views::{ filter::{ - internal::{CreateFilter, CreateNodeFilter}, + internal::CreateFilter, model::{node_filter::NodeFilter, property_filter::PropertyFilter, Windowed}, }, window_graph::WindowedGraph, @@ -28,7 +28,7 @@ use raphtory_api::{ inherit::Base, }; use raphtory_storage::{ - core_ops::{CoreGraphOps, InheritCoreGraphOps}, + core_ops::InheritCoreGraphOps, graph::{graph::GraphStorage, nodes::node_ref::NodeStorageRef}, }; @@ -52,6 +52,7 @@ impl NodePropertyFilteredGraph { impl CreateFilter for PropertyFilter> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph>; + type NodeFilter<'graph, G: GraphView + 'graph> = NodePropertyFilteredGraph>; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, @@ -71,11 +72,20 @@ impl CreateFilter for PropertyFilter> { filter, )) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.create_filter(graph) + } } impl CreateFilter for PropertyFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NodePropertyFilteredGraph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -83,6 +93,13 @@ impl CreateFilter for PropertyFilter { let prop_id = self.resolve_prop_id(graph.node_meta(), false)?; Ok(NodePropertyFilteredGraph::new(graph, prop_id, self)) } + + fn create_node_filter<'graph, G: GraphView + 'graph>( + self, + graph: G, + ) -> Result, GraphError> { + self.create_filter(graph) + } } impl NodeOp for NodePropertyFilteredGraph { @@ -96,14 +113,6 @@ impl NodeOp for NodePropertyFilteredGraph { } } -impl CreateNodeFilter for PropertyFilter { - type NodeFilter = NodePropertyFilteredGraph; - - fn create_node_filter(self, graph: G) -> Result, GraphError> { - self.create_filter(graph) - } -} - impl Base for NodePropertyFilteredGraph { type Base = G; 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 index 09d78fcd00..0f9352a0b0 100644 --- a/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs @@ -1,17 +1,13 @@ use crate::{ core::entities::LayerIds, - db::{ - api::{ - properties::internal::InheritPropertiesOps, - view::internal::{ - Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, - InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, - InheritTimeSemantics, InternalNodeFilterOps, Static, - }, + 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; @@ -46,24 +42,6 @@ impl<'graph, G: GraphViewOps<'graph>> NodeTypeFilteredGraph { } } -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 {} 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..9244f5b6ba 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::NodeFilterOp, view::internal::{ FilterOps, GraphView, Immutable, InheritEdgeHistoryFilter, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, @@ -9,7 +10,10 @@ use crate::{ InternalExplodedEdgeFilterOps, InternalNodeFilterOps, Static, }, }, - graph::views::filter::{internal::CreateFilter, model::not_filter::NotFilter}, + graph::views::filter::{ + internal::CreateFilter, + model::not_filter::{NotFilter, NotOp}, + }, }, errors::GraphError, prelude::GraphViewOps, @@ -38,6 +42,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 +54,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..69649b3140 100644 --- a/raphtory/src/db/graph/views/filter/or_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/or_filtered_graph.rs @@ -2,13 +2,18 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::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}, + graph::views::filter::{ + internal::CreateFilter, + model::or_filter::{OrFilter, OrOp}, + }, }, errors::GraphError, prelude::GraphViewOps, @@ -38,6 +43,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 +56,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/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index 540cafdc98..12dc27bc99 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::Filter, *}, }, graph::edge::EdgeView, task::{ @@ -137,17 +137,16 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S, CS: ComputeState + 'a> Clon } } -impl<'graph, 'a: 'graph, Current, S, CS> BaseFilter<'graph> - for EvalEdgeView<'graph, 'a, Current, CS, S> +impl<'graph, 'a: 'graph, Current, S, CS> Filter<'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..5faa66a2c5 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::Filter, 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> Filter<'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..6e12298a9e 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::{Filter, GraphView}, BaseNodeViewOps, BoxedLIter, IntoDynBoxed, }, }, @@ -358,17 +358,17 @@ 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> Filter<'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 +383,17 @@ where } } -impl<'graph, 'a, Current, S, CS> BaseFilter<'graph> for EvalNodeView<'graph, 'a, Current, S, CS> +impl<'graph, 'a, Current, S, CS> Filter<'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 ddd273eca5..8b37aaddf3 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -282,7 +282,7 @@ impl PyEdgeFilter { #[staticmethod] fn window(start: PyTime, end: PyTime) -> PyResult { - Ok(PyEdgeWindow(Windowed::::from_times(start, end))) + Ok(PyEdgeWindow(Windowed::from_times(start, end, EdgeFilter))) } } diff --git a/raphtory/src/python/filter/exploded_edge_filter_builder.rs b/raphtory/src/python/filter/exploded_edge_filter_builder.rs index 00993d7fc3..3076ba12a5 100644 --- a/raphtory/src/python/filter/exploded_edge_filter_builder.rs +++ b/raphtory/src/python/filter/exploded_edge_filter_builder.rs @@ -284,9 +284,11 @@ impl PyExplodedEdgeFilter { #[staticmethod] fn window(start: PyTime, end: PyTime) -> PyResult { - Ok(PyExplodedEdgeWindow( - Windowed::::from_times(start, end), - )) + Ok(PyExplodedEdgeWindow(Windowed::from_times( + start, + end, + ExplodedEdgeFilter, + ))) } } 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/node_filter_builders.rs b/raphtory/src/python/filter/node_filter_builders.rs index f05a075b74..1bf9b932bc 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -185,8 +185,8 @@ impl PyNodeFilter { #[staticmethod] fn window(py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyNodeWindow(Windowed::::from_times( - py_start, py_end, + Ok(PyNodeWindow(Windowed::from_times( + py_start, py_end, NodeFilter, ))) } } diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index e40743fc0e..b67b6052a8 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -2,10 +2,9 @@ use crate::{ db::graph::views::filter::{ internal::CreateFilter, model::{ - edge_filter::EndpointWrapper, property_filter::{ - ElemQualifierOps, InternalPropertyFilterOps, ListAggOps, MetadataFilterBuilder, - PropertyFilterBuilder, PropertyFilterOps, + ElemQualifierOps, ListAggOps, MetadataFilterBuilder, PropertyFilterBuilder, + PropertyFilterOps, }, TryAsCompositeFilter, }, @@ -13,7 +12,7 @@ use crate::{ 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; @@ -53,23 +52,23 @@ pub trait DynFilterOps: Send + Sync { prefix_match: bool, ) -> PyFilterExpr; - fn any(&self) -> PyResult; + 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 @@ -147,40 +146,40 @@ where ))) } - fn any(&self) -> PyResult { - Ok(PyFilterOps::wrap(ElemQualifierOps::any(self))) + fn any(&self) -> PyFilterOps { + PyFilterOps::wrap(ElemQualifierOps::any(self)) } - 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)) } } @@ -268,39 +267,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() } } @@ -372,3 +371,40 @@ where Bound::new(py, obj) } } + +// impl<'py, T: Clone> IntoPyObject<'py> for PropertyFilter +// where +// PropertyFilter: CreateFilter + TryAsCompositeFilter, +// { +// type Target = PyFilterExpr; +// type Output = Bound<'py, Self::Target>; +// type Error = PyErr; +// +// fn into_pyobject(self, py: Python<'py>) -> Result { +// PyFilterExpr(Arc::new(self)).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)) + } +} + +// impl<'py, M> IntoPyObject<'py> for OpChainBuilder +// where +// PropertyFilter: CreateFilter + TryAsCompositeFilter, +// { +// type Target = PyPropertyFilterBuilder; +// type Output = Bound<'py, Self::Target>; +// type Error = PyErr; +// +// fn into_pyobject(self, py: Python<'py>) -> Result { +// PyPropertyFilterBuilder::from_arc(Arc::new(self)).into_pyobject(py) +// } +// } diff --git a/raphtory/src/python/graph/edges.rs b/raphtory/src/python/graph/edges.rs index 048a78ba53..4e705ae14c 100644 --- a/raphtory/src/python/graph/edges.rs +++ b/raphtory/src/python/graph/edges.rs @@ -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 5c3c5beb98..3ede479cc2 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -10,7 +10,7 @@ use crate::{ ops, ops::{ filter::NO_FILTER, Degree, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, - NodeTypeFilter, + NodeTypeFilterOp, }, LazyNodeState, NodeStateOps, }, @@ -26,7 +26,7 @@ use crate::{ node::NodeView, nodes::Nodes, path::{PathFromGraph, PathFromNode}, - views::filter::model::AndFilter, + views::filter::model::and_filter::AndOp, }, }, errors::GraphError, @@ -34,7 +34,7 @@ use crate::{ python::{ filter::filter_expr::PyFilterExpr, graph::{ - node::internal::BaseFilter, + node::internal::Filter, properties::{MetadataView, PropertiesView, PyMetadataListList, PyNestedPropsIterable}, }, types::{iterable::FromIterable, repr::StructReprBuilder, wrappers::iterables::*}, @@ -452,10 +452,10 @@ impl PyMutableNode { #[derive(Clone)] #[pyclass(name = "Nodes", module = "raphtory", frozen)] pub struct PyNodes { - pub(crate) nodes: Nodes<'static, DynamicGraph, DynNodeFilter>, + pub(crate) nodes: Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>, } -impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph, DynNodeFilter> { +impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { Ok(ob.downcast::()?.get().nodes.clone()) } @@ -464,11 +464,12 @@ impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph, DynNodeFilter> { impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let nodes = &ob.downcast::()?.get().nodes; - if nodes.node_select.is_filtered() { + if nodes.selector.is_filtered() { Err(PyTypeError::new_err("Expected unfiltered nodes")) } else { Ok(Nodes::new_filtered( - nodes.base_graph().clone(), + nodes.base_graph.clone(), + nodes.graph.clone(), NO_FILTER, nodes.nodes.clone(), )) @@ -479,7 +480,7 @@ impl<'py> FromPyObject<'py> for Nodes<'static, DynamicGraph> { impl_nodeviewops!( PyNodes, nodes, - Nodes<'static, DynamicGraph, DynNodeFilter>, + Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter>, "Nodes", "NestedEdges", "PathFromGraph" @@ -513,20 +514,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 select = value.node_select.into_dynamic(); - let base_graph = value.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, select, value.nodes), + nodes: Nodes::new_filtered(base_graph, graph, select, value.nodes), } } } -impl<'py, G: StaticGraphViewOps + IntoDynamic, GH: IntoDynNodeOp> 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>; @@ -577,7 +586,7 @@ impl PyNodes { /// Returns: /// IdView: a view of the node ids #[getter] - fn id(&self) -> LazyNodeState<'static, ops::Id, DynamicGraph, DynNodeFilter> { + fn id(&self) -> LazyNodeState<'static, ops::Id, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.id() } @@ -586,7 +595,7 @@ impl PyNodes { /// Returns: /// NameView: a view of the node names #[getter] - fn name(&self) -> LazyNodeState<'static, ops::Name, DynamicGraph, DynNodeFilter> { + fn name(&self) -> LazyNodeState<'static, ops::Name, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.name() } @@ -597,7 +606,13 @@ impl PyNodes { #[getter] fn earliest_time( &self, - ) -> LazyNodeState<'static, ops::EarliestTime, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState< + 'static, + ops::EarliestTime, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.earliest_time() } @@ -612,6 +627,7 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynamicGraph, DynNodeFilter, > { self.nodes.earliest_date_time() @@ -624,7 +640,13 @@ impl PyNodes { #[getter] fn latest_time( &self, - ) -> LazyNodeState<'static, ops::LatestTime, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState< + 'static, + ops::LatestTime, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.latest_time() } @@ -639,6 +661,7 @@ impl PyNodes { 'static, ops::Map, Option>>, DynamicGraph, + DynamicGraph, DynNodeFilter, > { self.nodes.latest_date_time() @@ -651,7 +674,8 @@ impl PyNodes { /// fn history( &self, - ) -> LazyNodeState<'static, ops::History, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState<'static, ops::History, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.history() } @@ -661,8 +685,13 @@ impl PyNodes { /// EdgeHistoryCountView: a view of the edge history counts fn edge_history_count( &self, - ) -> LazyNodeState<'static, ops::EdgeHistoryCount, DynamicGraph, DynNodeFilter> - { + ) -> LazyNodeState< + 'static, + ops::EdgeHistoryCount, + DynamicGraph, + DynamicGraph, + DynNodeFilter, + > { self.nodes.edge_history_count() } @@ -671,7 +700,9 @@ impl PyNodes { /// Returns: /// NodeTypeView: a view of the node types #[getter] - fn node_type(&self) -> LazyNodeState<'static, ops::Type, DynamicGraph, DynNodeFilter> { + fn node_type( + &self, + ) -> LazyNodeState<'static, ops::Type, DynamicGraph, DynamicGraph, DynNodeFilter> { self.nodes.node_type() } @@ -686,6 +717,7 @@ impl PyNodes { 'static, ops::Map, Option>>>, DynamicGraph, + DynamicGraph, DynNodeFilter, > { self.nodes.history_date_time() @@ -717,7 +749,8 @@ impl PyNodes { /// DegreeView: a view of the undirected node degrees fn degree( &self, - ) -> LazyNodeState<'static, ops::Degree, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.degree() } @@ -727,7 +760,8 @@ impl PyNodes { /// DegreeView: a view of the in-degrees of the nodes fn in_degree( &self, - ) -> LazyNodeState<'static, ops::Degree, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.in_degree() } @@ -737,7 +771,8 @@ impl PyNodes { /// DegreeView: a view of the out-degrees of the nodes fn out_degree( &self, - ) -> LazyNodeState<'static, ops::Degree, DynamicGraph, DynNodeFilter> { + ) -> LazyNodeState<'static, Degree, DynamicGraph, DynamicGraph, DynNodeFilter> + { self.nodes.out_degree() } @@ -824,12 +859,14 @@ impl PyNodes { pub fn type_filter( &self, node_types: Vec, - ) -> Nodes<'static, DynamicGraph, AndFilter> { + ) -> Nodes<'static, DynamicGraph, DynamicGraph, AndOp> { self.nodes.type_filter(&node_types) } } -impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + '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 0615bb1c5a..75dbf44c6e 100644 --- a/raphtory/src/python/graph/node_state/node_state.rs +++ b/raphtory/src/python/graph/node_state/node_state.rs @@ -11,7 +11,10 @@ use crate::{ }, view::{DynamicGraph, GraphViewOps}, }, - graph::{node::NodeView, nodes::Nodes}, + graph::{ + node::NodeView, + nodes::{IntoDynNodes, Nodes}, + }, }, prelude::*, py_borrowing_iter, @@ -49,7 +52,7 @@ macro_rules! impl_node_state_ops { /// /// Returns: /// Nodes: The nodes - fn nodes(&self) -> Nodes<'static, DynamicGraph, DynNodeFilter> { + fn nodes(&self) -> Nodes<'static, DynamicGraph, DynamicGraph, DynNodeFilter> { self.inner.nodes().into_dyn() } @@ -320,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, DynNodeFilter>, + inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter>, } impl $name { - pub fn inner(&self) -> &LazyNodeState<'static, $op, DynamicGraph, DynNodeFilter> { + pub fn inner( + &self, + ) -> &LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter> { &self.inner } } @@ -351,16 +356,16 @@ macro_rules! impl_lazy_node_state { impl_node_state_ops!( $name, <$op as NodeOp>::Output, - LazyNodeState<'static, $op, DynamicGraph, DynNodeFilter>, + LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, DynNodeFilter>, |v: <$op as NodeOp>::Output| v, $computed, $py_value ); impl - From> for $name + From> for $name { - fn from(inner: LazyNodeState<'static, $op, DynamicGraph, F>) -> Self { + fn from(inner: LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, F>) -> Self { $name { inner: inner.into_dyn(), } @@ -368,7 +373,7 @@ macro_rules! impl_lazy_node_state { } impl<'py, F: IntoDynNodeOp + NodeFilterOp + 'static> pyo3::IntoPyObject<'py> - for LazyNodeState<'static, $op, DynamicGraph, F> + for LazyNodeState<'static, $op, DynamicGraph, DynamicGraph, F> { type Target = $name; type Output = Bound<'py, Self::Target>; @@ -379,7 +384,9 @@ macro_rules! impl_lazy_node_state { } } - impl<'py> FromPyObject<'py> for LazyNodeState<'static, $op, DynamicGraph, DynNodeFilter> { + 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()) } diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index d5d1f9f09e..005ec49a97 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -5,7 +5,7 @@ use crate::{ properties::{Metadata, Properties}, view::{ filter_ops::BaseFilterOps, - internal::{BaseFilter, DynamicGraph, IntoDynHop, IntoDynamic, MaterializedGraph}, + internal::{DynamicGraph, Filter, IntoDynHop, IntoDynamic, MaterializedGraph}, LayerOps, StaticGraphViewOps, }, }, diff --git a/raphtory/src/python/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs index 4b624cda44..e3c6285b51 100644 --- a/raphtory/src/python/packages/algorithms.rs +++ b/raphtory/src/python/packages/algorithms.rs @@ -59,10 +59,7 @@ use crate::{ }, db::{ api::{ - state::{ - ops::{filter::NO_FILTER, DynNodeFilter, DynNodeOp}, - Index, NodeState, - }, + state::{ops::filter::NO_FILTER, Index, NodeState}, view::internal::DynamicGraph, }, graph::{node::NodeView, nodes::Nodes}, @@ -780,7 +777,7 @@ pub fn k_core( } else { Some(Index::from_iter(v_set)) }; - Nodes::new_filtered(graph.graph.clone(), NO_FILTER, index) + 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..c071a64029 100644 --- a/raphtory/src/python/types/macros/trait_impl/filter_ops.rs +++ b/raphtory/src/python/types/macros/trait_impl/filter_ops.rs @@ -19,8 +19,7 @@ macro_rules! impl_filter_ops { fn filter( &self, filter: PyFilterExpr, - ) -> Result<<$base_type as BaseFilter<'static>>::Filtered, GraphError> - { + ) -> Result<<$base_type as Filter<'static>>::Filtered, GraphError> { 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 99fb7a2340..661909891e 100644 --- a/raphtory/src/python/types/repr.rs +++ b/raphtory/src/python/types/repr.rs @@ -272,8 +272,13 @@ impl Repr for &R { } } -impl<'graph, G: GraphViewOps<'graph>, GH: NodeFilterOp + '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, { diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index ec1c9a6acf..075713e284 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -8,13 +8,12 @@ use crate::{ model::{ edge_filter::{CompositeEdgeFilter, EdgeFilter}, property_filter::PropertyRef, - Filter, }, }, }, }, errors::GraphError, - prelude::{EdgeViewOps, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, + prelude::{GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, search::{ collectors::unique_entity_filter_collector::UniqueEntityFilterCollector, fallback_filter_edges, fields, get_reader, graph_index::Index, diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs index 96f0a3832a..35ac73ef83 100644 --- a/raphtory/src/search/mod.rs +++ b/raphtory/src/search/mod.rs @@ -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 89c9aec93b..577d838ff8 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::StaticGraphViewOps, graph::{ - edge::EdgeView, node::NodeView, views::filter::{ internal::CreateFilter, @@ -26,10 +25,8 @@ use itertools::Itertools; use raphtory_api::core::{entities::VID, storage::timeindex::AsTime}; use std::{collections::HashSet, sync::Arc}; use tantivy::{ - collector::{Collector, Count, TopDocs}, - query::Query, - schema::Value, - DocAddress, Document, IndexReader, Score, Searcher, TantivyDocument, + collector::Collector, query::Query, schema::Value, DocAddress, Document, IndexReader, Score, + Searcher, TantivyDocument, }; #[derive(Clone, Copy)] @@ -346,15 +343,14 @@ impl<'a> NodeFilterExecutor<'a> { graph: &G, node_ids: HashSet, ) -> Result>, GraphError> { - println!("filter {:?}", filter); - 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) From adf8d75915658a3949b0387fb3acb3e903b8a62e Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:30:40 +0000 Subject: [PATCH 31/42] redone --- raphtory-benchmark/benches/search_bench.rs | 6 +- raphtory-graphql/src/model/graph/edge.rs | 2 +- raphtory-graphql/src/model/graph/edges.rs | 4 +- raphtory-graphql/src/model/graph/graph.rs | 2 +- raphtory-graphql/src/model/graph/node.rs | 2 +- raphtory-graphql/src/model/graph/nodes.rs | 2 +- .../src/model/graph/path_from_node.rs | 2 +- raphtory/src/db/api/view/filter_ops.rs | 10 +- raphtory/src/db/api/view/graph.rs | 2 +- .../src/db/api/view/internal/base_filter.rs | 8 +- .../src/db/api/view/internal/into_dynamic.rs | 6 +- raphtory/src/db/api/view/layer.rs | 4 +- raphtory/src/db/api/view/mod.rs | 2 +- raphtory/src/db/api/view/time.rs | 8 +- raphtory/src/db/graph/assertions.rs | 2 +- raphtory/src/db/graph/edge.rs | 4 +- raphtory/src/db/graph/edges.rs | 10 +- raphtory/src/db/graph/node.rs | 4 +- raphtory/src/db/graph/nodes.rs | 4 +- raphtory/src/db/graph/path.rs | 10 +- .../views/filter/edge_node_filtered_graph.rs | 83 +-- .../filter/edge_property_filtered_graph.rs | 3 +- .../filter/exploded_edge_property_filter.rs | 2 +- raphtory/src/db/graph/views/filter/mod.rs | 14 +- .../graph/views/filter/model/edge_filter.rs | 675 ++---------------- .../filter/model/exploded_edge_filter.rs | 551 +------------- .../src/db/graph/views/filter/model/mod.rs | 253 ++++++- .../graph/views/filter/model/node_filter.rs | 179 +++-- .../views/filter/model/property_filter.rs | 255 ++++--- .../filter/node_property_filtered_graph.rs | 3 +- .../views/filter/node_type_filtered_graph.rs | 4 +- raphtory/src/db/graph/views/window_graph.rs | 5 +- raphtory/src/db/task/edge/eval_edge.rs | 4 +- raphtory/src/db/task/edge/eval_edges.rs | 4 +- raphtory/src/db/task/node/eval_node.rs | 6 +- raphtory/src/python/graph/edges.rs | 2 +- raphtory/src/python/graph/node.rs | 2 +- raphtory/src/python/graph/views/graph_view.rs | 4 +- raphtory/src/search/edge_filter_executor.rs | 2 +- .../search/exploded_edge_filter_executor.rs | 2 +- raphtory/src/search/mod.rs | 2 +- 41 files changed, 702 insertions(+), 1447 deletions(-) diff --git a/raphtory-benchmark/benches/search_bench.rs b/raphtory-benchmark/benches/search_bench.rs index 85aea2baa6..6f8af1bf67 100644 --- a/raphtory-benchmark/benches/search_bench.rs +++ b/raphtory-benchmark/benches/search_bench.rs @@ -10,7 +10,7 @@ use raphtory::{ properties::internal::{ InternalMetadataOps, InternalTemporalPropertiesOps, InternalTemporalPropertyViewOps, }, - view::{BaseFilterOps, SearchableGraphOps}, + view::{Filter, SearchableGraphOps}, }, graph::{ edge::EdgeView, @@ -20,7 +20,7 @@ use raphtory::{ filter_operator::{FilterOperator, FilterOperator::*}, node_filter::{NodeFilter, NodeFilterBuilderOps}, property_filter::{ - InternalPropertyFilterOps, PropertyFilterBuilder, PropertyFilterOps, + InternalPropertyFilterBuilderOps, PropertyFilterBuilder, PropertyFilterOps, }, ComposableFilter, PropertyFilterFactory, }, @@ -200,7 +200,7 @@ fn convert_to_property_filter( ) -> Option> where M: PropertyFilterFactory + Default, - PropertyFilterBuilder: PropertyFilterOps + InternalPropertyFilterOps, + PropertyFilterBuilder: PropertyFilterOps + InternalPropertyFilterBuilderOps, { let mut rng = thread_rng(); diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index 5ae11ea44d..f5cc2e8b76 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::{Filter, DynamicGraph, EdgeViewOps, 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 ca402db4e1..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::Filter, 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/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index 1cc6246899..7ee62c6aa7 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -28,7 +28,7 @@ use raphtory::{ api::{ properties::dyn_props::DynProperties, view::{ - BaseFilterOps, DynamicGraph, IntoDynamic, IterFilterOps, NodeViewOps, + Filter, DynamicGraph, IntoDynamic, Select, NodeViewOps, SearchableGraphOps, StaticGraphViewOps, TimeOps, }, }, 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 d11e3b88ad..de3e1c282d 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -17,7 +17,7 @@ use raphtory::{ db::{ api::{ state::{ops::DynNodeFilter, Index}, - view::{BaseFilterOps, DynamicGraph, IterFilterOps}, + view::{Filter, DynamicGraph, Select}, }, graph::{ nodes::{IntoDynNodes, Nodes}, diff --git a/raphtory-graphql/src/model/graph/path_from_node.rs b/raphtory-graphql/src/model/graph/path_from_node.rs index 153ccf5484..da06280318 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::{Filter, DynamicGraph, Select}, graph::{path::PathFromNode, views::filter::model::node_filter::CompositeNodeFilter}, }, errors::GraphError, diff --git a/raphtory/src/db/api/view/filter_ops.rs b/raphtory/src/db/api/view/filter_ops.rs index de5af7fc7d..7af5cf63ad 100644 --- a/raphtory/src/db/api/view/filter_ops.rs +++ b/raphtory/src/db/api/view/filter_ops.rs @@ -1,12 +1,12 @@ use crate::{ db::{ - api::view::internal::{Filter, IterFilter}, + api::view::internal::{InternalFilter, InternalSelect}, graph::views::filter::internal::CreateFilter, }, errors::GraphError, }; -pub trait BaseFilterOps<'graph>: Filter<'graph> { +pub trait Filter<'graph>: InternalFilter<'graph> { fn filter( &self, filter: F, @@ -15,7 +15,7 @@ pub trait BaseFilterOps<'graph>: Filter<'graph> { } } -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: Filter<'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 f81a35e481..0f38b294ac 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -922,7 +922,7 @@ pub trait StaticGraphViewOps: GraphView + 'static {} impl StaticGraphViewOps for G {} -impl<'graph, G> Filter<'graph> for G +impl<'graph, G> InternalFilter<'graph> for G where G: GraphViewOps<'graph> + 'graph, { diff --git a/raphtory/src/db/api/view/internal/base_filter.rs b/raphtory/src/db/api/view/internal/base_filter.rs index 3ecb2ee09d..9b181cf108 100644 --- a/raphtory/src/db/api/view/internal/base_filter.rs +++ b/raphtory/src/db/api/view/internal/base_filter.rs @@ -1,9 +1,9 @@ use crate::prelude::GraphViewOps; -pub trait Filter<'graph> { +pub trait InternalFilter<'graph> { type Graph: GraphViewOps<'graph> + 'graph; - type Filtered + 'graph>: Filter< + type Filtered + 'graph>: InternalFilter< 'graph, Graph = FilteredGraph, >; @@ -16,10 +16,10 @@ pub trait Filter<'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 7a00be3029..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::{DynamicGraph, Filter, Static}, + internal::{DynamicGraph, InternalFilter, Static}, BoxableGraphView, StaticGraphViewOps, }; use std::sync::Arc; @@ -26,11 +26,11 @@ impl IntoDynamic for Arc { } } -pub trait IntoDynHop: Filter<'static, Graph: 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/layer.rs b/raphtory/src/db/api/view/layer.rs index f24f63e9b2..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::{Filter, InternalLayerOps}, + api::view::internal::{InternalFilter, InternalLayerOps}, graph::views::layer_graph::LayeredGraph, }, errors::GraphError, @@ -39,7 +39,7 @@ pub trait LayerOps<'graph> { fn num_layers(&self) -> usize; } -impl<'graph, V: Filter<'graph> + 'graph> LayerOps<'graph> for V { +impl<'graph, V: InternalFilter<'graph> + 'graph> LayerOps<'graph> for V { type LayeredViewType = V::Filtered>; fn default_layer(&self) -> Self::LayeredViewType { 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/time.rs b/raphtory/src/db/api/view/time.rs index e2a8903893..663405e122 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::{Filter, GraphTimeSemanticsOps, InternalMaterialize}, + internal::{InternalFilter, GraphTimeSemanticsOps, InternalMaterialize}, time::internal::InternalTimeOps, }, }; @@ -18,7 +18,7 @@ use std::{ pub(crate) mod internal { use crate::{ - db::{api::view::internal::Filter, graph::views::window_graph::WindowedGraph}, + db::{api::view::internal::InternalFilter, graph::views::window_graph::WindowedGraph}, prelude::{GraphViewOps, TimeOps}, }; use std::cmp::{max, min}; @@ -34,7 +34,7 @@ pub(crate) mod internal { end: Option, ) -> Self::InternalWindowedView; } - impl<'graph, E: Filter<'graph> + 'graph> InternalTimeOps<'graph> for E { + impl<'graph, E: InternalFilter<'graph> + 'graph> InternalTimeOps<'graph> for E { type InternalWindowedView = E::Filtered>; fn timeline_start(&self) -> Option { @@ -157,7 +157,7 @@ pub trait TimeOps<'graph>: ParseTimeError: From<>::Error>; } -impl<'graph, V: Filter<'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 b3327aa1cf..17a24bc408 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::{EdgeTimeSemanticsOps, Filter, GraphView, Static}, + internal::{EdgeTimeSemanticsOps, InternalFilter, GraphView, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -637,7 +637,7 @@ impl From> for EdgeRef { } } -impl<'graph, Current> Filter<'graph> for EdgeView +impl<'graph, Current> InternalFilter<'graph> for EdgeView where Current: GraphViewOps<'graph>, { diff --git a/raphtory/src/db/graph/edges.rs b/raphtory/src/db/graph/edges.rs index 1e4c6b4a36..5676a5b619 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::{Filter, FilterOps, IterFilter, Static}, + internal::{InternalFilter, FilterOps, InternalSelect, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -44,7 +44,7 @@ impl<'graph, G: GraphViewOps<'graph>> Debug for Edges<'graph, G> { } } -impl<'graph, Current> Filter<'graph> for Edges<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for Edges<'graph, Current> where Current: GraphViewOps<'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, { @@ -264,7 +264,7 @@ impl<'graph, G: GraphViewOps<'graph>> NestedEdges<'graph, G> { } } -impl<'graph, Current> Filter<'graph> for NestedEdges<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for NestedEdges<'graph, Current> where Current: GraphViewOps<'graph>, { @@ -356,7 +356,7 @@ impl<'graph, G: GraphViewOps<'graph>> BaseEdgeViewOps<'graph> for NestedEdges<'g } } -impl<'graph, G> IterFilter<'graph> for NestedEdges<'graph, G> +impl<'graph, G> InternalSelect<'graph> for NestedEdges<'graph, G> where G: GraphViewOps<'graph> + 'graph, { diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index cdf0cdd822..1b4e8732f5 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::{Filter, GraphTimeSemanticsOps, NodeTimeSemanticsOps, Static}, + internal::{InternalFilter, GraphTimeSemanticsOps, NodeTimeSemanticsOps, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -172,7 +172,7 @@ impl<'graph, G: GraphViewOps<'graph>> NodeView<'graph, G> { } } -impl<'graph, Current> Filter<'graph> for NodeView<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for NodeView<'graph, Current> where Current: GraphViewOps<'graph>, { diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 1b7599ac0c..429e448257 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -7,7 +7,7 @@ use crate::{ Index, LazyNodeState, }, view::{ - internal::{Filter, FilterOps, NodeList, Static}, + internal::{InternalFilter, FilterOps, NodeList, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, }, }, @@ -389,7 +389,7 @@ where } } -impl<'graph, G, Current, F> Filter<'graph> for Nodes<'graph, G, Current, F> +impl<'graph, G, Current, F> InternalFilter<'graph> for Nodes<'graph, G, Current, F> where G: GraphViewOps<'graph> + 'graph, Current: GraphViewOps<'graph> + 'graph, diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index 6a363fe68e..482895c1fa 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::{Filter, FilterOps, IterFilter, Static}, + internal::{InternalFilter, FilterOps, InternalSelect, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, @@ -203,7 +203,7 @@ impl<'graph, G: GraphViewOps<'graph>> IntoIterator for PathFromGraph<'graph, G> } } -impl<'graph, Current> Filter<'graph> for PathFromGraph<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for PathFromGraph<'graph, Current> where Current: GraphViewOps<'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,7 +423,7 @@ impl<'graph, G: GraphViewOps<'graph>> IntoIterator for PathFromNode<'graph, G> { } } -impl<'graph, Current> Filter<'graph> for PathFromNode<'graph, Current> +impl<'graph, Current> InternalFilter<'graph> for PathFromNode<'graph, Current> where Current: GraphViewOps<'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/edge_node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs index 622377d8c6..4dc7a355db 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -1,3 +1,5 @@ +use crate::db::api::state::ops::NodeFilterOp; +use crate::db::api::view::internal::{GraphView, InternalNodeFilterOps}; use crate::{ db::{ api::{ @@ -23,16 +25,16 @@ use raphtory_storage::{ pub struct EdgeNodeFilteredGraph { graph: G, endpoint: Endpoint, - filtered_graph: F, + filter: F, } impl EdgeNodeFilteredGraph { #[inline] - pub fn new(graph: G, endpoint: Endpoint, filtered_graph: F) -> Self { + pub fn new(graph: G, endpoint: Endpoint, filter: F) -> Self { Self { graph, endpoint, - filtered_graph, + filter, } } } @@ -48,58 +50,20 @@ impl Base for EdgeNodeFilteredGraph { impl Static for EdgeNodeFilteredGraph {} impl Immutable for EdgeNodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritCoreGraphOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritStorageOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritLayerOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritListOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritMaterialize - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritNodeFilterOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritPropertiesOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritTimeSemantics - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritNodeHistoryFilter - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritEdgeHistoryFilter - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritEdgeLayerFilterOps - for EdgeNodeFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InheritExplodedEdgeFilterOps - 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<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InternalEdgeFilterOps - for EdgeNodeFilteredGraph -{ +impl InternalEdgeFilterOps for EdgeNodeFilteredGraph { #[inline] fn internal_edge_filtered(&self) -> bool { true @@ -116,14 +80,11 @@ impl<'graph, G: GraphViewOps<'graph>, F: GraphViewOps<'graph>> InternalEdgeFilte return false; } - let src_binding = self.graph.core_node(edge.src()); - let dst_binding = self.graph.core_node(edge.dst()); - let node_ref = match self.endpoint { - Endpoint::Src => src_binding.as_ref(), - Endpoint::Dst => dst_binding.as_ref(), + let vid = match self.endpoint { + Endpoint::Src => edge.src(), + Endpoint::Dst => edge.dst(), }; - self.filtered_graph - .internal_filter_node(node_ref, layer_ids) + 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 992df3520f..88776d4fd5 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 @@ -147,7 +147,7 @@ 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}, @@ -169,6 +169,7 @@ mod test_edge_property_filtered_graph { use proptest::{arbitrary::any, proptest}; use raphtory_api::core::entities::properties::prop::PropType; use raphtory_storage::mutation::addition_ops::InternalAdditionOps; + use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; #[test] fn test_edge_filter2() { 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 3c36c86ad8..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 @@ -190,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, diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index f716afcfb0..e6a28f1cff 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -136,7 +136,7 @@ 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, @@ -480,7 +480,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, @@ -2330,6 +2330,8 @@ pub(crate) mod test_filters { }; use raphtory_api::core::entities::properties::prop::Prop; use std::vec; + use crate::db::graph::views::filter::model::property_filter::ElemQualifierOps; + use crate::db::graph::views::filter::model::TemporalPropertyFilterFactory; #[test] fn test_exact_match() { @@ -7705,6 +7707,10 @@ pub(crate) mod test_filters { }, }, }; + use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; + use crate::db::graph::views::filter::model::node_filter::NodeIdFilterBuilderOps; + use crate::db::graph::views::filter::model::{PropertyFilterFactory, TemporalPropertyFilterFactory}; + use crate::db::graph::views::filter::model::property_filter::ListAggOps; #[test] fn test_filter_edges_src_property_eq() { @@ -8860,6 +8866,8 @@ pub(crate) mod test_filters { }, }; use raphtory_api::core::entities::properties::prop::Prop; + use crate::db::graph::views::filter::model::property_filter::ElemQualifierOps; + use crate::db::graph::views::filter::model::TemporalPropertyFilterFactory; #[test] fn test_filter_edges_for_property_eq() { @@ -10228,9 +10236,9 @@ pub(crate) mod test_filters { ComposableFilter, PropertyFilterFactory, TryAsCompositeFilter, }, test_filters::{init_edges_graph, IdentityGraphTransformer}, - // EdgeFieldFilter, }, }; + use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; // #[test] // fn test_filter_edge_for_src_dst() { 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 a70eb6ace2..cfa46a5188 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,3 +1,5 @@ +use crate::db::graph::views::filter::model::node_filter::{InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps}; +use crate::db::graph::views::filter::model::{Filter, PropertyFilterFactory, Wrap}; use crate::{ db::{ api::{ @@ -10,13 +12,12 @@ use crate::{ model::{ exploded_edge_filter::CompositeExplodedEdgeFilter, node_filter::{ - CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps, NodeIdFilter, - NodeIdFilterBuilder, NodeIdFilterBuilderOps, NodeNameFilter, - NodeNameFilterBuilder, NodeTypeFilter, NodeTypeFilterBuilder, + CompositeNodeFilter, NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, + NodeTypeFilterBuilder, }, property_filter::{ - InternalPropertyFilterOps, MetadataFilterBuilder, Op, OpChainBuilder, - PropertyFilter, PropertyFilterBuilder, PropertyFilterOps, PropertyRef, + InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, + PropertyFilterBuilder, PropertyRef, }, AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, }, @@ -25,7 +26,6 @@ use crate::{ errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::entities::{properties::prop::Prop, GID}; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; @@ -40,7 +40,7 @@ pub enum CompositeEdgeFilter { Src(CompositeNodeFilter), Dst(CompositeNodeFilter), Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), + Windowed(Box>), And(Box, Box), Or(Box, Box), Not(Box), @@ -52,7 +52,7 @@ impl Display for CompositeEdgeFilter { 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), @@ -74,23 +74,23 @@ impl CreateFilter for CompositeEdgeFilter { ) -> Result, GraphError> { match self { CompositeEdgeFilter::Src(filter) => { - let filtered_graph = filter.create_filter(graph.clone())?; + let filter = filter.create_node_filter(graph.clone())?; Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Src, - filtered_graph, + filter, ))) } CompositeEdgeFilter::Dst(filter) => { - let filtered_graph = filter.create_filter(graph.clone())?; + let filter = filter.create_node_filter(graph.clone())?; Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Dst, - filtered_graph, + filter, ))) } CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeEdgeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_filter(graph)?)), + CompositeEdgeFilter::Windowed(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeEdgeFilter::And(l, r) => { let (l, r) = (*l, *r); Ok(Arc::new( @@ -140,13 +140,13 @@ pub struct EdgeFilter; impl EdgeFilter { #[inline] - pub fn src() -> EdgeEndpoint { - EdgeEndpoint::src() + pub fn src() -> EndpointWrapper { + EndpointWrapper::new(NodeFilter, Endpoint::Src) } #[inline] - pub fn dst() -> EdgeEndpoint { - EdgeEndpoint::dst() + pub fn dst() -> EndpointWrapper { + EndpointWrapper::new(NodeFilter, Endpoint::Dst) } #[inline] @@ -155,62 +155,6 @@ impl EdgeFilter { } } -// Endpoint selector that exposes **node** filter builders for src/dst. -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub struct EdgeEndpoint(Endpoint); - -impl EdgeEndpoint { - #[inline] - pub fn src() -> Self { - Self(Endpoint::Src) - } - - #[inline] - pub fn dst() -> Self { - Self(Endpoint::Dst) - } - - #[inline] - pub fn id(&self) -> EndpointWrapper { - EndpointWrapper::new(NodeFilter::id(), self.0) - } - - #[inline] - pub fn name(&self) -> EndpointWrapper { - EndpointWrapper::new(NodeFilter::name(), self.0) - } - - #[inline] - pub fn node_type(&self) -> EndpointWrapper { - EndpointWrapper::new(NodeFilter::node_type(), self.0) - } - - #[inline] - pub fn property( - &self, - name: impl Into, - ) -> PropertyFilterBuilder> { - PropertyFilterBuilder::new(name.into(), EndpointWrapper::new(NodeFilter, self.0)) - } - - #[inline] - pub fn metadata( - &self, - name: impl Into, - ) -> MetadataFilterBuilder> { - MetadataFilterBuilder::new(name.into(), EndpointWrapper::new(NodeFilter, self.0)) - } - - #[inline] - pub fn window( - &self, - start: S, - end: E, - ) -> EndpointWrapper> { - EndpointWrapper::new(NodeFilter::window(start, end), self.0) - } -} - // 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 @@ -233,14 +177,6 @@ impl EndpointWrapper { endpoint: self.endpoint, } } - - #[inline] - pub fn with(&self, inner: U) -> EndpointWrapper { - EndpointWrapper { - inner, - endpoint: self.endpoint, - } - } } impl Display for EndpointWrapper { @@ -249,67 +185,26 @@ impl Display for EndpointWrapper { } } -impl TryAsCompositeFilter for EndpointWrapper -where - T: TryAsCompositeFilter + Clone, -{ - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - - fn try_as_composite_edge_filter(&self) -> Result { - let filter = self.inner.try_as_composite_node_filter()?; - Ok(match self.endpoint { - Endpoint::Src => CompositeEdgeFilter::Src(filter), - Endpoint::Dst => CompositeEdgeFilter::Dst(filter), - }) - } - - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) +impl EndpointWrapper { + #[inline] + pub fn id(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::id(), self.endpoint) } -} - -impl CreateFilter for EndpointWrapper -where - T: TryAsCompositeFilter + Clone, -{ - type EntityFiltered<'graph, G: GraphViewOps<'graph>> - = Arc - where - T: 'graph; - type NodeFilter<'graph, G: GraphView + 'graph> - = NotANodeFilter - where - Self: 'graph; - - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> - where - T: 'graph, - { - let filter = self.try_as_composite_edge_filter()?; - filter.create_filter(graph) + #[inline] + pub fn name(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::name(), self.endpoint) } - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - _graph: G, - ) -> Result, GraphError> - where - Self: 'graph, - { - Err(GraphError::NotNodeFilter) + #[inline] + pub fn node_type(&self) -> EndpointWrapper { + EndpointWrapper::new(NodeFilter::node_type(), self.endpoint) } } -impl InternalPropertyFilterOps for EndpointWrapper { +impl InternalPropertyFilterBuilderOps for EndpointWrapper { type Marker = T::Marker; + #[inline] fn property_ref(&self) -> PropertyRef { self.inner.property_ref() @@ -326,516 +221,90 @@ impl InternalPropertyFilterOps for EndpointWrapper } } -impl EndpointWrapper { - #[inline] - pub fn eq(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.eq(v)) - } - - #[inline] - pub fn ne(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.ne(v)) - } - - #[inline] - pub fn le(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.le(v)) - } - - #[inline] - pub fn ge(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.ge(v)) - } - - #[inline] - pub fn lt(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.lt(v)) - } - - #[inline] - pub fn gt(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.gt(v)) - } - - #[inline] - pub fn is_in( - &self, - vals: impl IntoIterator, - ) -> EndpointWrapper> { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in( - &self, - vals: impl IntoIterator, - ) -> EndpointWrapper> { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn is_none(&self) -> EndpointWrapper> { - self.with(self.inner.is_none()) - } - - #[inline] - pub fn is_some(&self) -> EndpointWrapper> { - self.with(self.inner.is_some()) - } - - #[inline] - pub fn starts_with(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.starts_with(v)) - } - - #[inline] - pub fn ends_with(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.ends_with(v)) - } - - #[inline] - pub fn contains(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.contains(v)) - } - - #[inline] - pub fn not_contains(&self, v: impl Into) -> EndpointWrapper> { - self.with(self.inner.not_contains(v)) - } - - #[inline] - pub fn fuzzy_search( - &self, - s: impl Into, - d: usize, - p: bool, - ) -> EndpointWrapper> { - self.with(self.inner.fuzzy_search(s, d, p)) - } -} - -impl EndpointWrapper> { - #[inline] - pub fn any(self) -> Self { - self.map(|b| b.any()) - } - - #[inline] - pub fn all(self) -> Self { - self.map(|b| b.all()) - } - - #[inline] - pub fn len(self) -> Self { - self.map(|b| b.len()) - } - - #[inline] - pub fn sum(self) -> Self { - self.map(|b| b.sum()) - } - - #[inline] - pub fn avg(self) -> Self { - self.map(|b| b.avg()) - } - - #[inline] - pub fn min(self) -> Self { - self.map(|b| b.min()) - } - - #[inline] - pub fn max(self) -> Self { - self.map(|b| b.max()) - } - - #[inline] - pub fn first(self) -> Self { - self.map(|b| b.first()) - } - - #[inline] - pub fn last(self) -> Self { - self.map(|b| b.last()) - } -} - -impl EndpointWrapper> -where - M: EntityMarker + Send + Sync + Clone + 'static, -{ - #[inline] - pub fn property( - &self, - name: impl Into, - ) -> PropertyFilterBuilder>> { - PropertyFilterBuilder::new( - name.into(), - EndpointWrapper::new(self.inner.clone(), self.endpoint), - ) - } - - #[inline] - pub fn metadata( - &self, - name: impl Into, - ) -> MetadataFilterBuilder>> { - MetadataFilterBuilder::new( - name.into(), - EndpointWrapper::new(self.inner.clone(), self.endpoint), - ) - } -} - -impl EndpointWrapper>> { - #[inline] - pub fn any(self) -> Self { - self.map(|b| b.any()) - } - - #[inline] - pub fn all(self) -> Self { - self.map(|b| b.all()) - } - - #[inline] - pub fn len(self) -> Self { - self.map(|b| b.len()) - } - - #[inline] - pub fn sum(self) -> Self { - self.map(|b| b.sum()) - } - - #[inline] - pub fn avg(self) -> Self { - self.map(|b| b.avg()) - } - - #[inline] - pub fn min(self) -> Self { - self.map(|b| b.min()) - } - - #[inline] - pub fn max(self) -> Self { - self.map(|b| b.max()) - } - - #[inline] - pub fn first(self) -> Self { - self.map(|b| b.first()) - } - - #[inline] - pub fn last(self) -> Self { - self.map(|b| b.last()) - } -} - -impl EndpointWrapper { - #[inline] - pub fn eq>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.eq(v)) - } - - #[inline] - pub fn ne>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.ne(v)) - } - - #[inline] - pub fn is_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - V: Into, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - V: Into, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn lt>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.lt(v)) - } - - #[inline] - pub fn le>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.le(v)) - } - - #[inline] - pub fn gt>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.gt(v)) - } - - #[inline] - pub fn ge>(&self, v: V) -> EndpointWrapper { - self.with(self.inner.ge(v)) - } - - // string-y id ops (if allowed by your NodeIdFilter validation) - #[inline] - pub fn starts_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.starts_with(s)) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.ends_with(s)) - } - - #[inline] - pub fn contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.contains(s)) - } - - #[inline] - pub fn not_contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.not_contains(s)) - } - - #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> EndpointWrapper { - self.with(self.inner.fuzzy_search(s, d, p)) - } -} - -impl EndpointWrapper { - #[inline] - pub fn eq>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.eq(s.into())) - } - - #[inline] - pub fn ne>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.ne(s.into())) - } - - #[inline] - pub fn is_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn starts_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.starts_with(s.into())) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.ends_with(s.into())) - } - - #[inline] - pub fn contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.contains(s.into())) - } - - #[inline] - pub fn not_contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.not_contains(s.into())) - } - - #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> EndpointWrapper { - self.with(self.inner.fuzzy_search(s.into(), d, p)) +impl InternalNodeFilterBuilderOps for EndpointWrapper { + fn field_name(&self) -> &'static str { + self.inner.field_name() } } -impl EndpointWrapper { - #[inline] - pub fn eq>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.eq(s.into())) - } - - #[inline] - pub fn ne>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.ne(s.into())) - } - - #[inline] - pub fn is_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> EndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn starts_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.starts_with(s.into())) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.ends_with(s.into())) - } - - #[inline] - pub fn contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.contains(s.into())) - } - - #[inline] - pub fn not_contains>(&self, s: S) -> EndpointWrapper { - self.with(self.inner.not_contains(s.into())) - } - - #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> EndpointWrapper { - self.with(self.inner.fuzzy_search(s.into(), d, p)) +impl InternalNodeIdFilterBuilderOps for EndpointWrapper { + fn field_name(&self) -> &'static str { + self.inner.field_name() } } -impl TryAsCompositeFilter for PropertyFilter> { +impl TryAsCompositeFilter for EndpointWrapper { fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) + Err(GraphError::NotNodeFilter) } fn try_as_composite_edge_filter(&self) -> Result { - let node_prop = PropertyFilter:: { - prop_ref: self.prop_ref.clone(), - prop_value: self.prop_value.clone(), - operator: self.operator.clone(), - ops: self.ops.clone(), - entity: NodeFilter, + let filter = self.inner.try_as_composite_node_filter()?; + let filter = match self.endpoint { + Endpoint::Src => CompositeEdgeFilter::Src(filter), + Endpoint::Dst => CompositeEdgeFilter::Dst(filter), }; - let node_cf = node_prop.try_as_composite_node_filter()?; - Ok(match self.entity.endpoint { - Endpoint::Src => CompositeEdgeFilter::Src(node_cf), - Endpoint::Dst => CompositeEdgeFilter::Dst(node_cf), - }) + Ok(filter) } fn try_as_composite_exploded_edge_filter( &self, ) -> Result { - Err(GraphError::NotSupported) + 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 CreateFilter for PropertyFilter> { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; +impl CreateFilter for EndpointWrapper { + type EntityFiltered<'graph, G> + = EdgeNodeFilteredGraph> + where + Self: 'graph, + G: GraphViewOps<'graph>; - type NodeFilter<'graph, G: GraphView + 'graph> + type NodeFilter<'graph, G> = NotANodeFilter where - Self: 'graph; + Self: 'graph, + G: GraphView + 'graph; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { - self.try_as_composite_edge_filter()?.create_filter(graph) + let filter = self.inner.create_node_filter(graph.clone())?; + Ok(EdgeNodeFilteredGraph::new(graph, self.endpoint, filter)) } fn create_node_filter<'graph, G: GraphView + 'graph>( self, _graph: G, - ) -> Result, GraphError> - where - Self: 'graph, - { + ) -> Result, GraphError> { Err(GraphError::NotNodeFilter) } } -impl TryAsCompositeFilter for PropertyFilter>> { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } +impl EntityMarker for EndpointWrapper where M: EntityMarker + Send + Sync + Clone + 'static {} - fn try_as_composite_edge_filter(&self) -> Result { - let node_prop = PropertyFilter::> { - prop_ref: self.prop_ref.clone(), - prop_value: self.prop_value.clone(), - operator: self.operator.clone(), - ops: self.ops.clone(), - entity: self.entity.inner.clone(), - }; - let node_cf = node_prop.try_as_composite_node_filter()?; - Ok(match self.entity.endpoint { - Endpoint::Src => CompositeEdgeFilter::Src(node_cf), - Endpoint::Dst => CompositeEdgeFilter::Dst(node_cf), - }) - } +impl Wrap for EdgeFilter { + type Wrapped = T; - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - Err(GraphError::NotSupported) + fn wrap(&self, value: T) -> Self::Wrapped { + value } } -impl CreateFilter for PropertyFilter>> { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = Arc; - - type NodeFilter<'graph, G: GraphView + 'graph> - = NotANodeFilter - where - Self: 'graph; +impl Wrap for EndpointWrapper { + type Wrapped = EndpointWrapper; - fn create_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - self.try_as_composite_edge_filter()?.create_filter(graph) - } - - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - _graph: G, - ) -> Result, GraphError> - where - Self: 'graph, - { - Err(GraphError::NotNodeFilter) + fn wrap(&self, inner: T) -> Self::Wrapped { + EndpointWrapper { + inner, + endpoint: self.endpoint, + } } } - -impl EntityMarker for EndpointWrapper where M: EntityMarker + Send + Sync + Clone + 'static {} 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 index 2e092963ec..1afadf60e7 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -10,13 +10,12 @@ use crate::{ model::{ edge_filter::{CompositeEdgeFilter, Endpoint}, node_filter::{ - CompositeNodeFilter, NodeFilter, NodeFilterBuilderOps, NodeIdFilter, - NodeIdFilterBuilder, NodeIdFilterBuilderOps, NodeNameFilter, - NodeNameFilterBuilder, NodeTypeFilter, NodeTypeFilterBuilder, + CompositeNodeFilter, NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, + NodeTypeFilterBuilder, }, property_filter::{ - InternalPropertyFilterOps, MetadataFilterBuilder, Op, OpChainBuilder, - PropertyFilter, PropertyFilterBuilder, PropertyFilterOps, PropertyRef, + InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, + PropertyFilterBuilder, PropertyRef, }, AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, }, @@ -25,16 +24,16 @@ use crate::{ errors::GraphError, prelude::GraphViewOps, }; -use raphtory_api::core::entities::{properties::prop::Prop, GID}; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; +use crate::db::graph::views::filter::model::Wrap; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeExplodedEdgeFilter { Src(CompositeNodeFilter), Dst(CompositeNodeFilter), Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), + Windowed(Box>), And( Box, @@ -53,7 +52,7 @@ impl Display for CompositeExplodedEdgeFilter { CompositeExplodedEdgeFilter::Src(filter) => write!(f, "SRC({})", filter), CompositeExplodedEdgeFilter::Dst(filter) => write!(f, "DST({})", filter), CompositeExplodedEdgeFilter::Property(filter) => write!(f, "{}", filter), - CompositeExplodedEdgeFilter::PropertyWindowed(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), @@ -75,23 +74,23 @@ impl CreateFilter for CompositeExplodedEdgeFilter { ) -> Result, GraphError> { match self { Self::Src(filter) => { - let filtered_graph = filter.create_filter(graph.clone())?; + let filter = filter.create_node_filter(graph.clone())?; Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Src, - filtered_graph, + filter, ))) } Self::Dst(filter) => { - let filtered_graph = filter.create_filter(graph.clone())?; + let filter = filter.create_node_filter(graph.clone())?; Ok(Arc::new(EdgeNodeFilteredGraph::new( graph, Endpoint::Dst, - filtered_graph, + filter, ))) } Self::Property(p) => Ok(Arc::new(p.create_filter(graph)?)), - Self::PropertyWindowed(pw) => Ok(Arc::new(pw.create_filter(graph)?)), + Self::Windowed(pw) => Ok(Arc::new(pw.create_filter(graph)?)), Self::And(l, r) => { let (l, r) = (*l, *r); // move out, no clone Ok(Arc::new( @@ -215,14 +214,6 @@ impl ExplodedEndpointWrapper { endpoint: self.endpoint, } } - - #[inline] - pub fn with(&self, inner: U) -> ExplodedEndpointWrapper { - ExplodedEndpointWrapper { - inner, - endpoint: self.endpoint, - } - } } impl Display for ExplodedEndpointWrapper { @@ -287,171 +278,6 @@ where } } -impl InternalPropertyFilterOps for 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() - } -} - -impl ExplodedEndpointWrapper { - #[inline] - pub fn eq(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.eq(v)) - } - - #[inline] - pub fn ne(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.ne(v)) - } - - #[inline] - pub fn le(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.le(v)) - } - - #[inline] - pub fn ge(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.ge(v)) - } - - #[inline] - pub fn lt(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.lt(v)) - } - - #[inline] - pub fn gt(&self, v: impl Into) -> ExplodedEndpointWrapper> { - self.with(self.inner.gt(v)) - } - - #[inline] - pub fn is_in( - &self, - vals: impl IntoIterator, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in( - &self, - vals: impl IntoIterator, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn is_none(&self) -> ExplodedEndpointWrapper> { - self.with(self.inner.is_none()) - } - - #[inline] - pub fn is_some(&self) -> ExplodedEndpointWrapper> { - self.with(self.inner.is_some()) - } - - #[inline] - pub fn starts_with( - &self, - v: impl Into, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.starts_with(v)) - } - - #[inline] - pub fn ends_with( - &self, - v: impl Into, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.ends_with(v)) - } - - #[inline] - pub fn contains( - &self, - v: impl Into, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.contains(v)) - } - - #[inline] - pub fn not_contains( - &self, - v: impl Into, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.not_contains(v)) - } - - #[inline] - pub fn fuzzy_search( - &self, - s: impl Into, - d: usize, - p: bool, - ) -> ExplodedEndpointWrapper> { - self.with(self.inner.fuzzy_search(s, d, p)) - } -} - -impl ExplodedEndpointWrapper> { - #[inline] - pub fn any(self) -> Self { - self.map(|b| b.any()) - } - - #[inline] - pub fn all(self) -> Self { - self.map(|b| b.all()) - } - - #[inline] - pub fn len(self) -> Self { - self.map(|b| b.len()) - } - - #[inline] - pub fn sum(self) -> Self { - self.map(|b| b.sum()) - } - - #[inline] - pub fn avg(self) -> Self { - self.map(|b| b.avg()) - } - - #[inline] - pub fn min(self) -> Self { - self.map(|b| b.min()) - } - - #[inline] - pub fn max(self) -> Self { - self.map(|b| b.max()) - } - - #[inline] - pub fn first(self) -> Self { - self.map(|b| b.first()) - } - - #[inline] - pub fn last(self) -> Self { - self.map(|b| b.last()) - } -} - impl ExplodedEndpointWrapper> where M: EntityMarker + Send + Sync + Clone + 'static, @@ -479,247 +305,21 @@ where } } -impl ExplodedEndpointWrapper>> { - #[inline] - pub fn any(self) -> Self { - self.map(|b| b.any()) - } - - #[inline] - pub fn all(self) -> Self { - self.map(|b| b.all()) - } - - #[inline] - pub fn len(self) -> Self { - self.map(|b| b.len()) - } - - #[inline] - pub fn sum(self) -> Self { - self.map(|b| b.sum()) - } - - #[inline] - pub fn avg(self) -> Self { - self.map(|b| b.avg()) - } - - #[inline] - pub fn min(self) -> Self { - self.map(|b| b.min()) - } - - #[inline] - pub fn max(self) -> Self { - self.map(|b| b.max()) - } - - #[inline] - pub fn first(self) -> Self { - self.map(|b| b.first()) - } - - #[inline] - pub fn last(self) -> Self { - self.map(|b| b.last()) - } -} - -impl ExplodedEndpointWrapper { - #[inline] - pub fn eq>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.eq(v)) - } - - #[inline] - pub fn ne>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.ne(v)) - } - - #[inline] - pub fn is_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - V: Into, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - V: Into, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn lt>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.lt(v)) - } - - #[inline] - pub fn le>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.le(v)) - } - - #[inline] - pub fn gt>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.gt(v)) - } - - #[inline] - pub fn ge>(&self, v: V) -> ExplodedEndpointWrapper { - self.with(self.inner.ge(v)) - } - - // string-y id ops (if allowed by your NodeIdFilter validation) - #[inline] - pub fn starts_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.starts_with(s)) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.ends_with(s)) - } - - #[inline] - pub fn contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.contains(s)) - } - - #[inline] - pub fn not_contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.not_contains(s)) - } - - #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> ExplodedEndpointWrapper { - self.with(self.inner.fuzzy_search(s, d, p)) - } -} - -impl ExplodedEndpointWrapper { - #[inline] - pub fn eq>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.eq(s.into())) - } - - #[inline] - pub fn ne>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.ne(s.into())) - } - - #[inline] - pub fn is_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn starts_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.starts_with(s.into())) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.ends_with(s.into())) - } - - #[inline] - pub fn contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.contains(s.into())) - } - - #[inline] - pub fn not_contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.not_contains(s.into())) - } - - #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> ExplodedEndpointWrapper { - self.with(self.inner.fuzzy_search(s.into(), d, p)) - } -} - -impl ExplodedEndpointWrapper { - #[inline] - pub fn eq>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.eq(s.into())) - } - - #[inline] - pub fn ne>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.ne(s.into())) - } - - #[inline] - pub fn is_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_in(vals)) - } - - #[inline] - pub fn is_not_in(&self, vals: I) -> ExplodedEndpointWrapper - where - I: IntoIterator, - { - self.with(self.inner.is_not_in(vals)) - } - - #[inline] - pub fn starts_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.starts_with(s.into())) - } - - #[inline] - pub fn ends_with>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.ends_with(s.into())) - } - +impl InternalPropertyFilterBuilderOps for ExplodedEndpointWrapper { + type Marker = T::Marker; #[inline] - pub fn contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.contains(s.into())) + fn property_ref(&self) -> PropertyRef { + self.inner.property_ref() } #[inline] - pub fn not_contains>(&self, s: S) -> ExplodedEndpointWrapper { - self.with(self.inner.not_contains(s.into())) + fn ops(&self) -> &[Op] { + self.inner.ops() } #[inline] - pub fn fuzzy_search>( - &self, - s: S, - d: usize, - p: bool, - ) -> ExplodedEndpointWrapper { - self.with(self.inner.fuzzy_search(s.into(), d, p)) + fn entity(&self) -> Self::Marker { + self.inner.entity() } } @@ -743,109 +343,26 @@ impl ExplodedEdgeFilter { } } -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 { - let node_prop = PropertyFilter:: { - prop_ref: self.prop_ref.clone(), - prop_value: self.prop_value.clone(), - operator: self.operator.clone(), - ops: self.ops.clone(), - entity: NodeFilter, - }; - let node_cf = node_prop.try_as_composite_node_filter()?; - Ok(match self.entity.endpoint { - Endpoint::Src => CompositeExplodedEdgeFilter::Src(node_cf), - Endpoint::Dst => CompositeExplodedEdgeFilter::Dst(node_cf), - }) - } +impl EntityMarker for ExplodedEndpointWrapper where + M: EntityMarker + Send + Sync + Clone + 'static +{ } -impl CreateFilter for PropertyFilter> { - 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> { - self.try_as_composite_exploded_edge_filter()? - .create_filter(graph) - } +impl Wrap for ExplodedEdgeFilter { + type Wrapped = T; - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - _graph: G, - ) -> Result, GraphError> { - Err(GraphError::NotNodeFilter) + fn wrap(&self, value: T) -> Self::Wrapped { + value } } -impl TryAsCompositeFilter for PropertyFilter>> { - fn try_as_composite_node_filter(&self) -> Result { - Err(GraphError::NotSupported) - } +impl Wrap for ExplodedEndpointWrapper { + type Wrapped = ExplodedEndpointWrapper; - fn try_as_composite_edge_filter(&self) -> Result { - Err(GraphError::NotSupported) - } - - fn try_as_composite_exploded_edge_filter( - &self, - ) -> Result { - let node_prop = PropertyFilter::> { - prop_ref: self.prop_ref.clone(), - prop_value: self.prop_value.clone(), - operator: self.operator.clone(), - ops: self.ops.clone(), - entity: self.entity.inner.clone(), - }; - let node_cf = node_prop.try_as_composite_node_filter()?; - Ok(match self.entity.endpoint { - Endpoint::Src => CompositeExplodedEdgeFilter::Src(node_cf), - Endpoint::Dst => CompositeExplodedEdgeFilter::Dst(node_cf), - }) - } -} - -impl CreateFilter for PropertyFilter>> { - 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> { - self.try_as_composite_exploded_edge_filter()? - .create_filter(graph) - } - - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - _graph: G, - ) -> Result, GraphError> { - Err(GraphError::NotNodeFilter) + fn wrap(&self, inner: T) -> Self::Wrapped { + ExplodedEndpointWrapper { + inner, + endpoint: self.endpoint, + } } } - -impl EntityMarker for ExplodedEndpointWrapper where - M: EntityMarker + Send + Sync + Clone + 'static -{ -} diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 6cb2f08d55..a758dc4a08 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -1,4 +1,16 @@ +use crate::db::api::view::internal::GraphView; +use crate::db::graph::views::filter::internal::CreateFilter; pub(crate) use crate::db::graph::views::filter::model::and_filter::AndFilter; +use crate::db::graph::views::filter::model::exploded_edge_filter::ExplodedEndpointWrapper; +use crate::db::graph::views::filter::model::node_filter::{ + InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, +}; +use crate::db::graph::views::filter::model::property_filter::{ + CombinedFilter, InternalPropertyFilterBuilderOps, Op, OpChainBuilder, PropertyFilterOps, + PropertyRef, +}; +use crate::db::graph::views::window_graph::WindowedGraph; +use crate::prelude::{GraphViewOps, TimeOps}; use crate::{ db::graph::views::filter::model::{ edge_filter::{CompositeEdgeFilter, EdgeFilter, EndpointWrapper}, @@ -11,6 +23,7 @@ use crate::{ }, errors::GraphError, }; +use raphtory_api::core::storage::timeindex::AsTime; use raphtory_api::core::{ entities::{GidRef, GID}, storage::timeindex::TimeIndexEntry, @@ -31,20 +44,36 @@ pub mod property_filter; pub struct Windowed { pub start: TimeIndexEntry, pub end: TimeIndexEntry, - pub marker: M, + 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, marker: M) -> Self { - Self { start, end, marker } + pub fn new(start: TimeIndexEntry, end: TimeIndexEntry, entity: M) -> Self { + Self { + start, + end, + inner: entity, + } } #[inline] - pub fn from_times(start: S, end: E, marker: M) -> 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, marker) + Self::new(s, e, entity) } } @@ -270,6 +299,86 @@ impl Filter { } } +impl InternalNodeFilterBuilderOps for Windowed { + 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 Marker = T::Marker; + + fn property_ref(&self) -> PropertyRef { + self.inner.property_ref() + } + + fn entity(&self) -> Self::Marker { + self.inner.entity() + } +} + +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, + ) -> Result { + let filter = self.inner.try_as_composite_exploded_edge_filter()?; + let filter = CompositeExplodedEdgeFilter::Windowed(Box::new(self.wrap(filter))); + Ok(filter) + } +} + +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())) + } +} + // Fluent Composite Filter Builder APIs pub trait TryAsCompositeFilter: Send + Sync { fn try_as_composite_node_filter(&self) -> Result; @@ -335,18 +444,136 @@ 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: Wrap { + type Entity: Clone + Send + Sync + 'static; + + fn entity(&self) -> Self::Entity; +} + +pub trait PropertyFilterFactory: InternalPropertyFilterFactory { + fn property( + &self, + name: impl Into, + ) -> Self::Wrapped> { + let builder = PropertyFilterBuilder::new(name, self.entity()); + self.wrap(builder) + } + + fn metadata( + &self, + name: impl Into, + ) -> Self::Wrapped> { + let builder = MetadataFilterBuilder::new(name, self.entity()); + self.wrap(builder) + } +} + +impl PropertyFilterFactory for ExplodedEndpointWrapper {} + +pub trait TemporalPropertyFilterFactory: InternalPropertyFilterBuilderOps { + fn temporal(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { + prop_ref: PropertyRef::TemporalProperty(self.property_ref().name().to_string()), + ops: vec![], + entity: self.entity(), + }; + self.wrap(builder) + } +} + +impl InternalPropertyFilterFactory for NodeFilter { + type Entity = NodeFilter; + + fn entity(&self) -> Self::Entity { + NodeFilter + } +} + +impl PropertyFilterFactory for NodeFilter {} + +impl InternalPropertyFilterFactory for EdgeFilter { + type Entity = EdgeFilter; + + fn entity(&self) -> Self::Entity { + EdgeFilter + } +} + +impl PropertyFilterFactory for EdgeFilter {} + +impl InternalPropertyFilterFactory for ExplodedEdgeFilter { + type Entity = ExplodedEdgeFilter; + + fn entity(&self) -> Self::Entity { + ExplodedEdgeFilter + } +} + +impl PropertyFilterFactory for ExplodedEdgeFilter {} + +impl InternalPropertyFilterFactory for Windowed { + type Entity = T::Entity; - fn metadata(&self, name: impl Into) -> MetadataFilterBuilder; + fn entity(&self) -> Self::Entity { + self.inner.entity() + } } -impl PropertyFilterFactory for M { - fn property(&self, name: impl Into) -> PropertyFilterBuilder { - PropertyFilterBuilder::new(name, self.clone()) +impl TemporalPropertyFilterFactory for Windowed {} + +impl PropertyFilterFactory for Windowed {} + +impl InternalPropertyFilterFactory for EndpointWrapper { + type Entity = T::Entity; + + fn entity(&self) -> Self::Entity { + self.inner.entity() } +} + +impl TemporalPropertyFilterFactory for EndpointWrapper {} - fn metadata(&self, name: impl Into) -> MetadataFilterBuilder { - MetadataFilterBuilder::new(name, self.clone()) +impl PropertyFilterFactory for EndpointWrapper {} + +impl InternalPropertyFilterFactory + for ExplodedEndpointWrapper +{ + type Entity = T::Entity; + + fn entity(&self) -> Self::Entity { + self.inner.entity() } } + +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 04fc76c316..d27abfa0d9 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,3 +1,4 @@ +use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::{ @@ -81,6 +82,8 @@ impl From for NodeNameFilter { impl CreateFilter for NodeNameFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeNameFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilteredGraph; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -88,8 +91,6 @@ impl CreateFilter for NodeNameFilter { Ok(NodeNameFilteredGraph::new(graph, self.0)) } - type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilteredGraph; - fn create_node_filter<'graph, G: GraphView + 'graph>( self, graph: G, @@ -116,6 +117,8 @@ impl From for NodeTypeFilter { impl CreateFilter for NodeTypeFilter { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeTypeFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NodeTypeFilterOp; + fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, @@ -130,8 +133,6 @@ impl CreateFilter for NodeTypeFilter { Ok(NodeTypeFilteredGraph::new(graph, node_types_filter.into())) } - type NodeFilter<'graph, G: GraphView + 'graph> = NodeTypeFilterOp; - fn create_node_filter<'graph, G: GraphView + 'graph>( self, graph: G, @@ -143,7 +144,6 @@ impl CreateFilter for NodeTypeFilter { .iter() .map(|k| self.0.matches(Some(k))) // TODO: _default check .collect::>(); - Ok(TypeId.mask(node_types_filter.into())) } } @@ -152,7 +152,7 @@ impl CreateFilter for NodeTypeFilter { pub enum CompositeNodeFilter { Node(Filter), Property(PropertyFilter), - PropertyWindowed(PropertyFilter>), + Windowed(Box>), And(Box, Box), Or(Box, Box), Not(Box), @@ -162,7 +162,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), @@ -190,7 +190,7 @@ impl CreateFilter for CompositeNodeFilter { } }, CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeNodeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_filter(graph)?)), + CompositeNodeFilter::Windowed(i) => Ok(Arc::new(i.create_filter(graph)?)), CompositeNodeFilter::And(l, r) => Ok(Arc::new( AndFilter { left: l.deref().clone(), @@ -226,7 +226,7 @@ impl CreateFilter for CompositeNodeFilter { } }, CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_node_filter(graph)?)), - CompositeNodeFilter::PropertyWindowed(i) => Ok(Arc::new(i.create_node_filter(graph)?)), + CompositeNodeFilter::Windowed(i) => Ok(Arc::new(i.create_node_filter(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())?, @@ -258,51 +258,55 @@ impl TryAsCompositeFilter for CompositeNodeFilter { } } -pub trait InternalNodeFilterBuilderOps: Send + Sync { - type NodeFilterType: From + CreateFilter + TryAsCompositeFilter + Clone + 'static; - +pub trait InternalNodeFilterBuilderOps: Send + Sync + Wrap { fn field_name(&self) -> &'static str; } impl InternalNodeFilterBuilderOps for Arc { - type NodeFilterType = T::NodeFilterType; - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } - 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(NodeNameFilter(filter)) } fn fuzzy_search( @@ -310,82 +314,92 @@ 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(NodeNameFilter(filter)) } } impl NodeFilterBuilderOps for T {} -pub trait InternalNodeIdFilterBuilderOps: Send + Sync { - type NodeIdFilterType: From + CreateFilter + TryAsCompositeFilter + Clone + 'static; - +pub trait InternalNodeIdFilterBuilderOps: Send + Sync + Wrap { fn field_name(&self) -> &'static str; } impl InternalNodeIdFilterBuilderOps for Arc { - type NodeIdFilterType = T::NodeIdFilterType; - fn field_name(&self) -> &'static str { self.deref().field_name() } } pub trait NodeIdFilterBuilderOps: InternalNodeIdFilterBuilderOps { - fn eq>(&self, value: T) -> Self::NodeIdFilterType { - Filter::eq_id(self.field_name(), value).into() + fn eq>(&self, value: T) -> Self::Wrapped { + let filter = Filter::eq_id(self.field_name(), value); + self.wrap(NodeIdFilter(filter)) } - fn ne>(&self, value: T) -> Self::NodeIdFilterType { - 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)) } - fn is_in(&self, values: I) -> Self::NodeIdFilterType + 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)) } - fn is_not_in(&self, values: I) -> Self::NodeIdFilterType + 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)) } - fn lt>(&self, value: V) -> Self::NodeIdFilterType { - 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)) } - fn le>(&self, value: V) -> Self::NodeIdFilterType { - 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)) } - fn gt>(&self, value: V) -> Self::NodeIdFilterType { - 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)) } - fn ge>(&self, value: V) -> Self::NodeIdFilterType { - 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)) } - fn starts_with>(&self, s: S) -> Self::NodeIdFilterType { - 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)) } - fn ends_with>(&self, s: S) -> Self::NodeIdFilterType { - 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)) } - fn contains>(&self, s: S) -> Self::NodeIdFilterType { - 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)) } - fn not_contains>(&self, s: S) -> Self::NodeIdFilterType { - 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)) } fn fuzzy_search>( @@ -393,8 +407,10 @@ pub trait NodeIdFilterBuilderOps: InternalNodeIdFilterBuilderOps { s: S, levenshtein_distance: usize, prefix_match: bool, - ) -> Self::NodeIdFilterType { - 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)) } } @@ -403,8 +419,15 @@ 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 { - type NodeIdFilterType = NodeIdFilter; #[inline] fn field_name(&self) -> &'static str { "node_id" @@ -415,24 +438,36 @@ impl InternalNodeIdFilterBuilderOps for NodeIdFilterBuilder { pub struct NodeNameFilterBuilder; impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { - type NodeFilterType = 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; - 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; @@ -615,3 +650,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/property_filter.rs b/raphtory/src/db/graph/views/filter/model/property_filter.rs index 69f6bf1d91..383a251f6f 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -1,3 +1,5 @@ +use crate::db::graph::views::filter::internal::CreateFilter; +use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::{ @@ -16,7 +18,7 @@ use crate::{ exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, filter_operator::FilterOperator, node_filter::{CompositeNodeFilter, NodeFilter}, - TryAsCompositeFilter, Windowed, + TryAsCompositeFilter, }, }, }, @@ -1343,7 +1345,11 @@ impl TryAsCompositeFilter for PropertyFilter { } } -pub trait InternalPropertyFilterOps: Send + Sync { +pub trait CombinedFilter: CreateFilter + TryAsCompositeFilter + Clone + 'static {} + +impl CombinedFilter for T {} + +pub trait InternalPropertyFilterBuilderOps: Send + Sync + Wrap { type Marker: Clone + Send + Sync + 'static; fn property_ref(&self) -> PropertyRef; @@ -1355,7 +1361,7 @@ pub trait InternalPropertyFilterOps: Send + Sync { fn entity(&self) -> Self::Marker; } -impl InternalPropertyFilterOps for Arc { +impl InternalPropertyFilterBuilderOps for Arc { type Marker = T::Marker; fn property_ref(&self) -> PropertyRef { @@ -1371,168 +1377,194 @@ impl InternalPropertyFilterOps for Arc { } } -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::Wrapped>; + fn ne(&self, value: impl Into) -> Self::Wrapped>; + fn le(&self, value: impl Into) -> Self::Wrapped>; + fn ge(&self, value: impl Into) -> Self::Wrapped>; + fn lt(&self, value: impl Into) -> Self::Wrapped>; + fn gt(&self, value: impl Into) -> Self::Wrapped>; + fn is_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped>; + fn is_not_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped>; + fn is_none(&self) -> Self::Wrapped>; + fn is_some(&self) -> Self::Wrapped>; + fn starts_with(&self, value: impl Into) -> Self::Wrapped>; + fn ends_with(&self, value: impl Into) -> Self::Wrapped>; + fn contains(&self, value: impl Into) -> Self::Wrapped>; + fn not_contains(&self, value: impl Into) -> Self::Wrapped>; fn fuzzy_search( &self, prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> PropertyFilter; + ) -> Self::Wrapped>; } -impl PropertyFilterOps for T { - fn eq(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { +impl PropertyFilterOps for T { + fn eq(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn ne(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ne(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn le(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn le(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn ge(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ge(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn lt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn lt(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn gt(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn gt(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter { + fn is_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped> { + 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.wrap(filter) } - fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { - PropertyFilter { + fn is_not_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped> { + 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.wrap(filter) } - fn is_none(&self) -> PropertyFilter { - PropertyFilter { + fn is_none(&self) -> Self::Wrapped> { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, operator: FilterOperator::IsNone, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.wrap(filter) } - fn is_some(&self) -> PropertyFilter { - PropertyFilter { + fn is_some(&self) -> Self::Wrapped> { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, operator: FilterOperator::IsSome, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.wrap(filter) } - fn starts_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn starts_with(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn ends_with(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn ends_with(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn contains(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } - fn not_contains(&self, value: impl Into) -> PropertyFilter { - PropertyFilter { + fn not_contains(&self, value: impl Into) -> Self::Wrapped> { + 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.wrap(filter) } fn fuzzy_search( @@ -1540,8 +1572,8 @@ impl PropertyFilterOps for T { prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> PropertyFilter { - PropertyFilter { + ) -> Self::Wrapped> { + let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(Prop::Str(ArcStr::from(prop_value.into()))), operator: FilterOperator::FuzzySearch { @@ -1550,20 +1582,33 @@ impl PropertyFilterOps for T { }, ops: self.ops().to_vec(), entity: self.entity(), - } + }; + self.wrap(filter) } } #[derive(Clone)] 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 { Self(prop.into(), entity) } } -impl InternalPropertyFilterOps for PropertyFilterBuilder { +impl InternalPropertyFilterBuilderOps for PropertyFilterBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, +{ type Marker = M; fn property_ref(&self) -> PropertyRef { @@ -1578,14 +1623,27 @@ impl InternalPropertyFilterOps for PropertyFil #[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, +{ type Marker = M; + fn property_ref(&self) -> PropertyRef { PropertyRef::Metadata(self.0.clone()) } @@ -1602,6 +1660,13 @@ 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); @@ -1650,7 +1715,11 @@ impl OpChainBuilder { } } -impl InternalPropertyFilterOps for OpChainBuilder { +impl InternalPropertyFilterBuilderOps for OpChainBuilder +where + M: Send + Sync + Clone + 'static, + PropertyFilter: CombinedFilter, +{ type Marker = M; fn property_ref(&self) -> PropertyRef { @@ -1666,7 +1735,7 @@ impl InternalPropertyFilterOps for OpChainBuil } } -pub trait ElemQualifierOps: InternalPropertyFilterOps { +pub trait ElemQualifierOps: InternalPropertyFilterBuilderOps { fn any(&self) -> OpChainBuilder where Self: Sized, @@ -1690,7 +1759,7 @@ pub trait ElemQualifierOps: InternalPropertyFilterOps { } } -impl ElemQualifierOps for T {} +impl ElemQualifierOps for T {} impl PropertyFilterBuilder { pub fn temporal(self) -> OpChainBuilder { @@ -1702,7 +1771,7 @@ impl PropertyFilterBuilder { } } -pub trait ListAggOps: InternalPropertyFilterOps + Sized { +pub trait ListAggOps: InternalPropertyFilterBuilderOps + Sized { fn len(&self) -> OpChainBuilder { OpChainBuilder { prop_ref: self.property_ref(), @@ -1760,46 +1829,4 @@ pub trait ListAggOps: InternalPropertyFilterOps + Sized { } } -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) - } -} - -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_property_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs index f0e9ef3e7f..86443a6943 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 @@ -52,6 +52,7 @@ impl NodePropertyFilteredGraph { impl CreateFilter for PropertyFilter> { type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph>; + type NodeFilter<'graph, G: GraphView + 'graph> = NodePropertyFilteredGraph>; fn create_filter<'graph, G: GraphViewOps<'graph>>( @@ -151,7 +152,7 @@ impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodePropertyFilt 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 index 0f9352a0b0..d2154d1bf5 100644 --- a/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs @@ -83,7 +83,7 @@ impl<'graph, G: GraphViewOps<'graph>> InternalNodeFilterOps for NodeTypeFiltered mod tests_node_type_filtered_subgraph { use crate::{ db::{ - api::view::filter_ops::BaseFilterOps, + api::view::filter_ops::Filter, graph::{ graph::assert_graph_equal, views::filter::model::{ @@ -447,7 +447,7 @@ mod tests_node_type_filtered_subgraph { mod test_edges_filters_node_type_filtered_subgraph { use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{internal::FilterOps, Filter, StaticGraphViewOps}, graph::views::filter::model::property_filter::PropertyFilterOps, }, prelude::{AdditionOps, NO_PROPS}, diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 8dccd89487..40b4c9a839 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,7 +3292,7 @@ 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, @@ -3311,6 +3311,7 @@ mod views_test { }; use raphtory_api::core::{entities::properties::prop::Prop, storage::arc_str::ArcStr}; use std::sync::Arc; + use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; fn init_graph( graph: G, diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index 12dc27bc99..6d3dd7968e 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::Filter, *}, + view::{internal::InternalFilter, *}, }, graph::edge::EdgeView, task::{ @@ -137,7 +137,7 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S, CS: ComputeState + 'a> Clon } } -impl<'graph, 'a: 'graph, Current, S, CS> Filter<'graph> for EvalEdgeView<'graph, 'a, Current, CS, S> +impl<'graph, 'a: 'graph, Current, S, CS> InternalFilter<'graph> for EvalEdgeView<'graph, 'a, Current, CS, S> where 'a: 'graph, Current: GraphViewOps<'graph>, diff --git a/raphtory/src/db/task/edge/eval_edges.rs b/raphtory/src/db/task/edge/eval_edges.rs index 5faa66a2c5..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::Filter, BaseEdgeViewOps, BoxedLIter}, + view::{internal::InternalFilter, BaseEdgeViewOps, BoxedLIter}, }, graph::edges::Edges, task::{ @@ -43,7 +43,7 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, CS: Clone, S> Clone } } -impl<'graph, 'a, Current, CS, S> Filter<'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>, diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs index 6e12298a9e..7ac71005df 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::{Filter, GraphView}, + internal::{InternalFilter, GraphView}, BaseNodeViewOps, BoxedLIter, IntoDynBoxed, }, }, @@ -358,7 +358,7 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S: 'static, CS: ComputeState + } } -impl<'graph, 'a, S, CS, Current> Filter<'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>, @@ -383,7 +383,7 @@ where } } -impl<'graph, 'a, Current, S, CS> Filter<'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>, diff --git a/raphtory/src/python/graph/edges.rs b/raphtory/src/python/graph/edges.rs index 4e705ae14c..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}, diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 3ede479cc2..cb2ef65895 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -34,7 +34,7 @@ use crate::{ python::{ filter::filter_expr::PyFilterExpr, graph::{ - node::internal::Filter, + node::internal::InternalFilter, properties::{MetadataView, PropertiesView, PyMetadataListList, PyNestedPropsIterable}, }, types::{iterable::FromIterable, repr::StructReprBuilder, wrappers::iterables::*}, diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index 005ec49a97..f5f7d5f152 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -4,8 +4,8 @@ use crate::{ api::{ properties::{Metadata, Properties}, view::{ - filter_ops::BaseFilterOps, - internal::{DynamicGraph, Filter, IntoDynHop, IntoDynamic, MaterializedGraph}, + filter_ops::Filter, + internal::{DynamicGraph, InternalFilter, IntoDynHop, IntoDynamic, MaterializedGraph}, LayerOps, StaticGraphViewOps, }, }, diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 075713e284..68e2b2ef4c 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{internal::FilterOps, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ diff --git a/raphtory/src/search/exploded_edge_filter_executor.rs b/raphtory/src/search/exploded_edge_filter_executor.rs index 3eddbe74ad..2f0de0d6c6 100644 --- a/raphtory/src/search/exploded_edge_filter_executor.rs +++ b/raphtory/src/search/exploded_edge_filter_executor.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{internal::FilterOps, BaseFilterOps, StaticGraphViewOps}, + api::view::{internal::FilterOps, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs index 35ac73ef83..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, From b3c767cec8c80731d27bcaebfa33f89afbd3dcbc Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:50:48 +0000 Subject: [PATCH 32/42] fix --- raphtory/src/db/graph/views/filter/mod.rs | 1 + .../src/db/graph/views/filter/model/edge_filter.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index e6a28f1cff..7c5a2607f1 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -8868,6 +8868,7 @@ pub(crate) mod test_filters { use raphtory_api::core::entities::properties::prop::Prop; use crate::db::graph::views::filter::model::property_filter::ElemQualifierOps; use crate::db::graph::views::filter::model::TemporalPropertyFilterFactory; + use crate::db::graph::views::filter::model::property_filter::ListAggOps; #[test] fn test_filter_edges_for_property_eq() { 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 cfa46a5188..c828abc246 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,5 +1,7 @@ -use crate::db::graph::views::filter::model::node_filter::{InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps}; -use crate::db::graph::views::filter::model::{Filter, PropertyFilterFactory, Wrap}; +use crate::db::graph::views::filter::model::node_filter::{ + InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, +}; +use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::{ @@ -16,8 +18,7 @@ use crate::{ NodeTypeFilterBuilder, }, property_filter::{ - InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, - PropertyFilterBuilder, PropertyRef, + InternalPropertyFilterBuilderOps, Op, PropertyFilter, PropertyRef, }, AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, }, @@ -227,7 +228,7 @@ impl InternalNodeFilterBuilderOps for EndpointW } } -impl InternalNodeIdFilterBuilderOps for EndpointWrapper { +impl InternalNodeIdFilterBuilderOps for EndpointWrapper { fn field_name(&self) -> &'static str { self.inner.field_name() } From 88222881eac31b4b8ee8191e9cf170bbee6a333b Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:51:22 +0000 Subject: [PATCH 33/42] more changes --- raphtory-graphql/src/model/graph/graph.rs | 1 - .../filter/model/exploded_edge_filter.rs | 85 ++++--------------- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index 7ee62c6aa7..e93fd482cf 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -582,7 +582,6 @@ impl GqlGraph { let self_clone = self.clone(); blocking_compute(move || { let f: CompositeNodeFilter = filter.try_into()?; - println!("filter {}", f); let nodes = self_clone.graph.search_nodes(f, limit, offset)?; let result = nodes.into_iter().map(|vv| vv.into()).collect(); Ok(result) 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 index 1afadf60e7..59ed6ab37d 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -10,8 +10,7 @@ use crate::{ model::{ edge_filter::{CompositeEdgeFilter, Endpoint}, node_filter::{ - CompositeNodeFilter, NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, - NodeTypeFilterBuilder, + CompositeNodeFilter, NodeFilter, }, property_filter::{ InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, @@ -26,6 +25,7 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; +use crate::db::graph::views::filter::model::node_filter::{InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps}; use crate::db::graph::views::filter::model::Wrap; #[derive(Debug, Clone, PartialEq, Eq)] @@ -134,67 +134,6 @@ impl TryAsCompositeFilter for CompositeExplodedEdgeFilter { } } -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub struct ExplodedEdgeEndpoint(Endpoint); - -impl ExplodedEdgeEndpoint { - #[inline] - pub fn src() -> Self { - Self(Endpoint::Src) - } - - #[inline] - pub fn dst() -> Self { - Self(Endpoint::Dst) - } - - #[inline] - pub fn id(&self) -> ExplodedEndpointWrapper { - ExplodedEndpointWrapper::new(NodeFilter::id(), self.0) - } - - #[inline] - pub fn name(&self) -> ExplodedEndpointWrapper { - ExplodedEndpointWrapper::new(NodeFilter::name(), self.0) - } - - #[inline] - pub fn node_type(&self) -> ExplodedEndpointWrapper { - ExplodedEndpointWrapper::new(NodeFilter::node_type(), self.0) - } - - #[inline] - pub fn property( - &self, - name: impl Into, - ) -> PropertyFilterBuilder> { - PropertyFilterBuilder::new( - name.into(), - ExplodedEndpointWrapper::new(NodeFilter, self.0), - ) - } - - #[inline] - pub fn metadata( - &self, - name: impl Into, - ) -> MetadataFilterBuilder> { - MetadataFilterBuilder::new( - name.into(), - ExplodedEndpointWrapper::new(NodeFilter, self.0), - ) - } - - #[inline] - pub fn window( - &self, - start: S, - end: E, - ) -> ExplodedEndpointWrapper> { - ExplodedEndpointWrapper::new(NodeFilter::window(start, end), self.0) - } -} - #[derive(Debug, Clone)] pub struct ExplodedEndpointWrapper { pub(crate) inner: T, @@ -328,13 +267,13 @@ pub struct ExplodedEdgeFilter; impl ExplodedEdgeFilter { #[inline] - pub fn src() -> ExplodedEdgeEndpoint { - ExplodedEdgeEndpoint::src() + pub fn src() -> ExplodedEndpointWrapper { + ExplodedEndpointWrapper::new(NodeFilter, Endpoint::Src) } #[inline] - pub fn dst() -> ExplodedEdgeEndpoint { - ExplodedEdgeEndpoint::dst() + pub fn dst() -> ExplodedEndpointWrapper { + ExplodedEndpointWrapper::new(NodeFilter, Endpoint::Dst) } #[inline] @@ -343,6 +282,18 @@ impl ExplodedEdgeFilter { } } +impl InternalNodeFilterBuilderOps for ExplodedEndpointWrapper { + 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 { From 0865b17f229fd74087b6a49a4e348d94a827b353 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Thu, 20 Nov 2025 17:11:07 +0100 Subject: [PATCH 34/42] start fixing infinite trait bound recursion --- raphtory/src/db/api/state/ops/filter.rs | 31 ++++++- raphtory/src/db/graph/views/filter/mod.rs | 30 +++---- .../graph/views/filter/model/node_filter.rs | 30 +++---- .../views/filter/node_id_filtered_graph.rs | 82 ------------------- .../views/filter/node_name_filtered_graph.rs | 55 +++---------- .../src/python/filter/edge_filter_builders.rs | 5 +- 6 files changed, 68 insertions(+), 165 deletions(-) delete mode 100644 raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs diff --git a/raphtory/src/db/api/state/ops/filter.rs b/raphtory/src/db/api/state/ops/filter.rs index 3253618969..81bdfff551 100644 --- a/raphtory/src/db/api/state/ops/filter.rs +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -1,9 +1,12 @@ -use crate::db::api::state::{ - ops::{Const, IntoDynNodeOp}, - NodeOp, +use crate::db::{ + api::state::{ + ops::{Const, IntoDynNodeOp}, + NodeOp, + }, + graph::views::filter::model::Filter, }; use raphtory_api::core::entities::VID; -use raphtory_storage::graph::graph::GraphStorage; +use raphtory_storage::graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}; use std::sync::Arc; #[derive(Clone, Debug)] @@ -36,3 +39,23 @@ impl> MaskOp for Op { } 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()) + } +} diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 7c5a2607f1..7b8f85f11a 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -5,7 +5,6 @@ pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; mod node_filtered_graph; -mod node_id_filtered_graph; pub mod node_name_filtered_graph; pub mod node_property_filtered_graph; pub mod node_type_filtered_graph; @@ -2322,16 +2321,14 @@ pub(crate) mod test_filters { model::{ node_filter::NodeFilter, not_filter::NotFilter, - property_filter::{ListAggOps, PropertyFilterOps}, - ComposableFilter, PropertyFilterFactory, + property_filter::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, + ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, }, test_filters::{init_nodes_graph, IdentityGraphTransformer}, }, }; use raphtory_api::core::entities::properties::prop::Prop; use std::vec; - use crate::db::graph::views::filter::model::property_filter::ElemQualifierOps; - use crate::db::graph::views::filter::model::TemporalPropertyFilterFactory; #[test] fn test_exact_match() { @@ -7699,7 +7696,10 @@ pub(crate) mod test_filters { assertions::{assert_filter_edges_results, assert_search_edges_results, TestVariants}, views::filter::{ model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, 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, @@ -7707,10 +7707,6 @@ pub(crate) mod test_filters { }, }, }; - use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; - use crate::db::graph::views::filter::model::node_filter::NodeIdFilterBuilderOps; - use crate::db::graph::views::filter::model::{PropertyFilterFactory, TemporalPropertyFilterFactory}; - use crate::db::graph::views::filter::model::property_filter::ListAggOps; #[test] fn test_filter_edges_src_property_eq() { @@ -8859,16 +8855,14 @@ pub(crate) mod test_filters { }, views::filter::{ model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, ComposableFilter, - PropertyFilterFactory, + edge_filter::EdgeFilter, + property_filter::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, + ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, }, test_filters::{init_edges_graph, IdentityGraphTransformer}, }, }; use raphtory_api::core::entities::properties::prop::Prop; - use crate::db::graph::views::filter::model::property_filter::ElemQualifierOps; - use crate::db::graph::views::filter::model::TemporalPropertyFilterFactory; - use crate::db::graph::views::filter::model::property_filter::ListAggOps; #[test] fn test_filter_edges_for_property_eq() { @@ -10233,13 +10227,13 @@ pub(crate) mod test_filters { }, views::filter::{ model::{ - edge_filter::EdgeFilter, 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}, }, }; - use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; // #[test] // fn test_filter_edge_for_src_dst() { 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 d27abfa0d9..d40b970560 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,9 +1,11 @@ -use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::{ state::{ - ops::{filter::MaskOp, NodeTypeFilterOp, TypeId}, + ops::{ + filter::{MaskOp, NodeIdFilterOp}, + NodeTypeFilterOp, TypeId, + }, NodeOp, }, view::{internal::GraphView, BoxableGraphView}, @@ -14,10 +16,10 @@ use crate::{ and_filter::AndOp, edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, not_filter::NotOp, or_filter::OrOp, property_filter::PropertyFilter, AndFilter, - Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, + Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, - node_id_filtered_graph::NodeIdFilteredGraph, - node_name_filtered_graph::NodeNameFilteredGraph, + node_filtered_graph::NodeFilteredGraph, + node_name_filtered_graph::NodeNameFilterOp, node_type_filtered_graph::NodeTypeFilteredGraph, }, }, @@ -44,23 +46,23 @@ impl From for NodeIdFilter { } impl CreateFilter for NodeIdFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeIdFilteredGraph; + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; - type NodeFilter<'graph, G: GraphView + 'graph> = NodeIdFilteredGraph; + 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(NodeIdFilteredGraph::new(graph, self.0)) + Ok(NodeFilteredGraph::new(graph, NodeIdFilterOp::new(self.0))) } fn create_node_filter<'graph, G: GraphView + 'graph>( self, - graph: G, + _graph: G, ) -> Result, GraphError> { - self.create_filter(graph) + Ok(NodeIdFilterOp::new(self.0)) } } @@ -80,22 +82,22 @@ impl From for NodeNameFilter { } impl CreateFilter for NodeNameFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeNameFilteredGraph; + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; - type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilteredGraph; + type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilterOp; fn create_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, ) -> Result, GraphError> { - Ok(NodeNameFilteredGraph::new(graph, self.0)) + Ok(NodeFilteredGraph::new(graph, NodeNameFilterOp::new(self.0))) } fn create_node_filter<'graph, G: GraphView + 'graph>( self, graph: G, ) -> Result, GraphError> { - self.create_filter(graph) + Ok(NodeNameFilterOp::new(self.0)) } } 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 9d0c8afbc1..0000000000 --- a/raphtory/src/db/graph/views/filter/node_id_filtered_graph.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::{ - db::{ - api::{ - properties::internal::InheritPropertiesOps, - state::NodeOp, - view::internal::{ - GraphView, Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, - InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, - InheritStorageOps, InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::model::Filter, - }, - prelude::GraphViewOps, -}; -use raphtory_api::{ - core::entities::{LayerIds, VID}, - inherit::Base, -}; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::{ - graph::GraphStorage, - 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 NodeOp for NodeIdFilteredGraph { - type Output = bool; - - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let layer_ids = self.graph.layer_ids(); - let nodes = storage.nodes(); - let node_ref = nodes.node(node); - self.internal_filter_node(node_ref, layer_ids) - } -} - -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 index 112f3cf108..2147348031 100644 --- a/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs @@ -17,6 +17,7 @@ use raphtory_api::{ core::entities::{LayerIds, VID}, inherit::Base, }; +use raphtory_api::core::storage::arc_str::OptionAsStr; use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{ @@ -24,60 +25,24 @@ use raphtory_storage::{ nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, }, }; +use raphtory_storage::core_ops::CoreGraphOps; #[derive(Debug, Clone)] -pub struct NodeNameFilteredGraph { - graph: G, +pub struct NodeNameFilterOp { filter: Filter, } -impl NodeNameFilteredGraph { - pub(crate) fn new(graph: G, filter: Filter) -> Self { - Self { graph, filter } +impl NodeNameFilterOp { + pub(crate) fn new(filter: Filter) -> Self { + Self { filter } } } -impl NodeOp for NodeNameFilteredGraph { +impl NodeOp for NodeNameFilterOp { type Output = bool; fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let layer_ids = self.graph.layer_ids(); - let nodes = storage.nodes(); - let node_ref = nodes.node(node); - self.internal_filter_node(node_ref, layer_ids) + let node_ref = storage.core_node(node); + self.filter.matches(node_ref.name().as_str()) } -} - -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())) - } -} +} \ No newline at end of file diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 8b37aaddf3..8fcee6c1e4 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -1,8 +1,9 @@ use crate::{ db::graph::views::filter::model::{ - edge_filter::{EdgeEndpoint, EdgeFilter, EndpointWrapper}, + edge_filter::{EdgeFilter, EndpointWrapper}, node_filter::{ - NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, NodeTypeFilterBuilder, + NodeFilter, NodeIdFilterBuilder, NodeIdFilterBuilderOps, NodeNameFilterBuilder, + NodeTypeFilterBuilder, }, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, From 76c817a0d867609cd49ebcf9b43a9cecadd275eb Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:38:13 +0000 Subject: [PATCH 35/42] rid filtered graphs --- raphtory/src/db/api/state/ops/filter.rs | 78 ++++++++++ raphtory/src/db/graph/views/filter/mod.rs | 4 +- .../graph/views/filter/model/node_filter.rs | 4 +- .../views/filter/node_name_filtered_graph.rs | 48 ------ .../filter/node_property_filtered_graph.rs | 140 +----------------- raphtory/src/python/graph/views/graph_view.rs | 4 +- 6 files changed, 90 insertions(+), 188 deletions(-) delete mode 100644 raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs diff --git a/raphtory/src/db/api/state/ops/filter.rs b/raphtory/src/db/api/state/ops/filter.rs index 81bdfff551..c41dcb948e 100644 --- a/raphtory/src/db/api/state/ops/filter.rs +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -8,6 +8,14 @@ use crate::db::{ use raphtory_api::core::entities::VID; use raphtory_storage::graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}; use std::sync::Arc; +use raphtory_api::core::storage::arc_str::OptionAsStr; +use raphtory_storage::core_ops::CoreGraphOps; +use crate::db::api::view::internal::GraphView; +use crate::db::graph::views::filter::internal::CreateFilter; +use crate::db::graph::views::filter::model::node_filter::NodeFilter; +use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; +use crate::errors::GraphError; +use crate::prelude::{GraphViewOps, PropertyFilter}; #[derive(Clone, Debug)] pub struct Mask { @@ -59,3 +67,73 @@ impl NodeOp for NodeIdFilterOp { 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()) + } +} \ No newline at end of file diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 7b8f85f11a..f6d78ab36b 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -4,9 +4,7 @@ pub mod edge_property_filtered_graph; pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; -mod node_filtered_graph; -pub mod node_name_filtered_graph; -pub mod node_property_filtered_graph; +pub(crate) mod node_filtered_graph; pub mod node_type_filtered_graph; pub mod not_filtered_graph; pub mod or_filtered_graph; 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 d40b970560..2336bf4639 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -19,7 +19,6 @@ use crate::{ Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, node_filtered_graph::NodeFilteredGraph, - node_name_filtered_graph::NodeNameFilterOp, node_type_filtered_graph::NodeTypeFilteredGraph, }, }, @@ -29,6 +28,7 @@ use crate::{ use raphtory_api::core::entities::{GidType, GID}; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; +use crate::db::api::state::ops::filter::NodeNameFilterOp; #[derive(Debug, Clone)] pub struct NodeIdFilter(pub Filter); @@ -95,7 +95,7 @@ impl CreateFilter for NodeNameFilter { fn create_node_filter<'graph, G: GraphView + 'graph>( self, - graph: G, + _graph: G, ) -> Result, GraphError> { Ok(NodeNameFilterOp::new(self.0)) } 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 2147348031..0000000000 --- a/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{ - db::{ - api::{ - properties::internal::InheritPropertiesOps, - state::NodeOp, - view::internal::{ - GraphView, Immutable, InheritAllEdgeFilterOps, InheritEdgeHistoryFilter, - InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, - InheritStorageOps, InheritTimeSemantics, InternalNodeFilterOps, Static, - }, - }, - graph::views::filter::model::Filter, - }, - prelude::GraphViewOps, -}; -use raphtory_api::{ - core::entities::{LayerIds, VID}, - inherit::Base, -}; -use raphtory_api::core::storage::arc_str::OptionAsStr; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::{ - graph::GraphStorage, - nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, - }, -}; -use raphtory_storage::core_ops::CoreGraphOps; - -#[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()) - } -} \ No newline at end of file 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 86443a6943..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 @@ -9,144 +9,18 @@ use crate::{ 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, VID}, - storage::timeindex::AsTime, - }, + core::storage::timeindex::AsTime, inherit::Base, }; -use raphtory_storage::{ - core_ops::InheritCoreGraphOps, - graph::{graph::GraphStorage, 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>; - - type NodeFilter<'graph, G: GraphView + '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, - )) - } - - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - graph: G, - ) -> Result, GraphError> { - self.create_filter(graph) - } -} - -impl CreateFilter for PropertyFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph; - - type NodeFilter<'graph, G: GraphView + '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)) - } - - fn create_node_filter<'graph, G: GraphView + 'graph>( - self, - graph: G, - ) -> Result, GraphError> { - self.create_filter(graph) - } -} - -impl NodeOp for NodePropertyFilteredGraph { - type Output = bool; - - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - let layer_ids = self.graph.layer_ids(); - let nodes = storage.nodes(); - let node_ref = nodes.node(node); - self.internal_filter_node(node_ref, layer_ids) - } -} - -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 { diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index f5f7d5f152..fefbd9d569 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -20,7 +20,6 @@ use crate::{ filter::{ edge_property_filtered_graph::EdgePropertyFilteredGraph, exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, - node_property_filtered_graph::NodePropertyFilteredGraph, node_type_filtered_graph::NodeTypeFilteredGraph, }, layer_graph::LayeredGraph, @@ -44,6 +43,7 @@ use pyo3::prelude::*; use raphtory_api::core::storage::arc_str::ArcStr; use rayon::prelude::*; use std::collections::HashMap; +use crate::db::api::state::ops::filter::NodePropertyFilterOp; impl<'py> IntoPyObject<'py> for MaterializedGraph { type Target = PyAny; @@ -155,7 +155,7 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for EdgePropert } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropertyFilteredGraph { +impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropertyFilterOp { type Target = PyGraphView; type Output = >::Output; type Error = >::Error; From e8eb6394c703305ceb7a0d90d0bef61cbefd7431 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:53:48 +0000 Subject: [PATCH 36/42] rid nodetypefilteredgraph --- raphtory/src/db/api/view/graph.rs | 12 +- raphtory/src/db/graph/views/filter/mod.rs | 1 - .../src/db/graph/views/filter/model/mod.rs | 2 +- .../graph/views/filter/model/node_filter.rs | 12 +- .../views/filter/node_type_filtered_graph.rs | 758 ------------------ 5 files changed, 14 insertions(+), 771 deletions(-) delete mode 100644 raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 0f38b294ac..cb6d489ae6 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -19,7 +19,7 @@ use crate::{ views::{ cached_view::CachedView, filter::{ - model::TryAsCompositeFilter, node_type_filtered_graph::NodeTypeFilteredGraph, + model::TryAsCompositeFilter, }, node_subgraph::NodeSubgraph, valid_graph::ValidGraph, @@ -50,6 +50,8 @@ use raphtory_storage::{ use rayon::prelude::*; use rustc_hash::FxHashSet; use std::sync::{atomic::Ordering, Arc}; +use crate::db::api::state::ops::NodeTypeFilterOp; +use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; /// This trait GraphViewOps defines operations for accessing /// information about a graph. The trait has associated types @@ -80,7 +82,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, @@ -443,10 +445,8 @@ 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 { diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index f6d78ab36b..c37917c328 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -5,7 +5,6 @@ pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; pub(crate) mod node_filtered_graph; -pub mod node_type_filtered_graph; pub mod not_filtered_graph; pub mod or_filtered_graph; diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index a758dc4a08..91b933d92f 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -6,7 +6,7 @@ use crate::db::graph::views::filter::model::node_filter::{ InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, }; use crate::db::graph::views::filter::model::property_filter::{ - CombinedFilter, InternalPropertyFilterBuilderOps, Op, OpChainBuilder, PropertyFilterOps, + CombinedFilter, InternalPropertyFilterBuilderOps, OpChainBuilder, PropertyFilterOps, PropertyRef, }; use crate::db::graph::views::window_graph::WindowedGraph; 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 2336bf4639..e32501f190 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,3 +1,4 @@ +use crate::db::api::state::ops::filter::NodeNameFilterOp; use crate::{ db::{ api::{ @@ -19,7 +20,6 @@ use crate::{ Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, node_filtered_graph::NodeFilteredGraph, - node_type_filtered_graph::NodeTypeFilteredGraph, }, }, errors::GraphError, @@ -28,7 +28,6 @@ use crate::{ use raphtory_api::core::entities::{GidType, GID}; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, ops::Deref, sync::Arc}; -use crate::db::api::state::ops::filter::NodeNameFilterOp; #[derive(Debug, Clone)] pub struct NodeIdFilter(pub Filter); @@ -82,7 +81,7 @@ impl From for NodeNameFilter { } impl CreateFilter for NodeNameFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; type NodeFilter<'graph, G: GraphView + 'graph> = NodeNameFilterOp; @@ -117,7 +116,7 @@ impl From for NodeTypeFilter { } impl CreateFilter for NodeTypeFilter { - type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeTypeFilteredGraph; + type EntityFiltered<'graph, G: GraphViewOps<'graph>> = NodeFilteredGraph; type NodeFilter<'graph, G: GraphView + 'graph> = NodeTypeFilterOp; @@ -132,7 +131,10 @@ impl CreateFilter for NodeTypeFilter { .iter() .map(|k| self.0.matches(Some(k))) // TODO: _default check .collect::>(); - Ok(NodeTypeFilteredGraph::new(graph, node_types_filter.into())) + Ok(NodeFilteredGraph::new( + graph, + TypeId.mask(node_types_filter.into()), + )) } fn create_node_filter<'graph, G: GraphView + 'graph>( 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 d2154d1bf5..0000000000 --- a/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs +++ /dev/null @@ -1,758 +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, - }, - }, - 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<'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::Filter, - 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, Filter, 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, - ); - } - } - } -} From bda5e7725d6740640d6b2c781ab8fe0a3c6a3446 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:11:11 +0000 Subject: [PATCH 37/42] add review suggestions --- raphtory/src/db/api/state/lazy_node_state.rs | 5 +- raphtory/src/db/api/state/ops/filter.rs | 82 +++++++++ raphtory/src/db/api/state/ops/mod.rs | 71 ++++++++ raphtory/src/db/api/state/ops/node.rs | 150 +--------------- raphtory/src/db/api/view/graph.rs | 2 +- .../internal/{base_filter.rs => filter.rs} | 0 raphtory/src/db/api/view/internal/mod.rs | 4 +- raphtory/src/db/graph/nodes.rs | 7 +- .../graph/views/filter/and_filtered_graph.rs | 3 +- .../views/filter/edge_node_filtered_graph.rs | 2 +- .../filter/edge_property_filtered_graph.rs | 30 ++-- .../filter/exploded_edge_property_filter.rs | 2 +- raphtory/src/db/graph/views/filter/mod.rs | 169 ++++++++---------- .../db/graph/views/filter/model/and_filter.rs | 6 - .../graph/views/filter/model/edge_filter.rs | 6 +- .../filter/model/exploded_edge_filter.rs | 6 +- .../graph/views/filter/model/node_filter.rs | 8 +- .../db/graph/views/filter/model/not_filter.rs | 3 - .../db/graph/views/filter/model/or_filter.rs | 6 - .../graph/views/filter/not_filtered_graph.rs | 3 +- .../graph/views/filter/or_filtered_graph.rs | 3 +- .../filter/exploded_edge_filter_builder.rs | 2 +- .../python/filter/property_filter_builders.rs | 3 +- raphtory/src/python/graph/node.rs | 6 +- raphtory/src/search/edge_filter_executor.rs | 18 +- raphtory/src/search/node_filter_executor.rs | 26 +-- 26 files changed, 290 insertions(+), 333 deletions(-) rename raphtory/src/db/api/view/internal/{base_filter.rs => filter.rs} (100%) diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index fbe6ef73e8..869f03d9e9 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -23,7 +23,7 @@ use std::{ }; #[derive(Clone)] -pub struct LazyNodeState<'graph, Op, G, GH, F = Const> { +pub struct LazyNodeState<'graph, Op, G, GH = G, F = Const> { nodes: Nodes<'graph, G, GH, F>, pub(crate) op: Op, } @@ -305,7 +305,7 @@ mod test { db::api::{ state::{ lazy_node_state::LazyNodeState, - ops::node::{Degree, NodeOp}, + ops::node::NodeOp, }, view::IntoDynamic, }, @@ -314,6 +314,7 @@ mod test { use raphtory_api::core::{entities::VID, Direction}; use raphtory_storage::core_ops::CoreGraphOps; use std::sync::Arc; + use crate::db::api::state::ops::Degree; struct TestWrapper(Op); #[test] diff --git a/raphtory/src/db/api/state/ops/filter.rs b/raphtory/src/db/api/state/ops/filter.rs index c41dcb948e..cad09adcfd 100644 --- a/raphtory/src/db/api/state/ops/filter.rs +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -10,7 +10,9 @@ use raphtory_storage::graph::{graph::GraphStorage, nodes::node_storage_ops::Node use std::sync::Arc; use raphtory_api::core::storage::arc_str::OptionAsStr; use raphtory_storage::core_ops::CoreGraphOps; +use crate::db::api::state::ops::TypeId; use crate::db::api::view::internal::GraphView; +use crate::db::graph::create_node_type_filter; use crate::db::graph::views::filter::internal::CreateFilter; use crate::db::graph::views::filter::model::node_filter::NodeFilter; use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; @@ -136,4 +138,84 @@ impl NodeOp for NodePropertyFilterOp { 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()); + } } \ No newline at end of file diff --git a/raphtory/src/db/api/state/ops/mod.rs b/raphtory/src/db/api/state/ops/mod.rs index d35734d497..6f77a8f071 100644 --- a/raphtory/src/db/api/state/ops/mod.rs +++ b/raphtory/src/db/api/state/ops/mod.rs @@ -6,3 +6,74 @@ mod properties; pub use history::*; pub use node::*; pub use properties::*; +use raphtory_api::core::Direction; +use raphtory_api::core::entities::VID; +use raphtory_storage::graph::graph::GraphStorage; +use raphtory_storage::graph::nodes::node_storage_ops::NodeStorageOps; +use raphtory_storage::layer_ops::InternalLayerOps; +use std::sync::Arc; +use std::ops::Deref; +use crate::db::api::view::internal::filtered_node::FilteredNodeStorageOps; +use crate::db::api::view::internal::{FilterOps, FilterState, GraphView}; + +#[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() + } +} \ No newline at end of file diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index 1c4c1cd167..6d3b258ba1 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,26 +1,15 @@ -use crate::db::{ - api::{ - state::ops::filter::{Mask, MaskOp}, - view::internal::{ - time_semantics::filtered_node::FilteredNodeStorageOps, FilterOps, FilterState, - GraphView, - }, - }, - graph::{ - create_node_type_filter, - views::filter::model::{and_filter::AndOp, not_filter::NotOp, or_filter::OrOp}, - }, -}; use raphtory_api::core::{ entities::{GID, VID}, - storage::arc_str::ArcStr, - Direction, + storage::arc_str::ArcStr + , }; use raphtory_storage::{ core_ops::CoreGraphOps, - graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}, + graph::graph::GraphStorage, }; -use std::{ops::Deref, sync::Arc}; +use std::sync::Arc; +use crate::db::api::state::ops::filter::{AndOp, NotOp, OrOp}; +use crate::db::api::state::ops::Map; pub trait NodeFilterOp: NodeOp + Clone { fn is_filtered(&self) -> bool; @@ -32,17 +21,6 @@ pub trait NodeFilterOp: NodeOp + Clone { fn not(self) -> NotOp; } -#[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") - } -} - impl + Clone> NodeFilterOp for Op { fn is_filtered(&self) -> bool { // If there is a const true value, it is not filtered @@ -115,47 +93,6 @@ where impl IntoDynNodeOp for Eq where Eq: NodeOp + 'static {} -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 {} - -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 {} - -impl NodeOp for NotOp -where - T: NodeOp, -{ - type Output = bool; - - fn apply(&self, storage: &GraphStorage, node: VID) -> Self::Output { - !self.0.apply(storage, node) - } -} - -impl IntoDynNodeOp for NotOp where Self: NodeOp + 'static {} - #[derive(Clone, Copy, Debug)] pub struct Const(pub V); @@ -204,6 +141,7 @@ impl IntoDynNodeOp for Id {} #[derive(Debug, Copy, Clone)] pub struct Type; + impl NodeOp for Type { type Output = Option; @@ -225,77 +163,3 @@ impl NodeOp for TypeId { } impl IntoDynNodeOp for TypeId {} - -#[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 {} - -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() - } -} - -#[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 {} - -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/view/graph.rs b/raphtory/src/db/api/view/graph.rs index cb6d489ae6..99038f2dc6 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -50,7 +50,7 @@ use raphtory_storage::{ use rayon::prelude::*; use rustc_hash::FxHashSet; use std::sync::{atomic::Ordering, Arc}; -use crate::db::api::state::ops::NodeTypeFilterOp; +use crate::db::api::state::ops::filter::NodeTypeFilterOp; use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; /// This trait GraphViewOps defines operations for accessing diff --git a/raphtory/src/db/api/view/internal/base_filter.rs b/raphtory/src/db/api/view/internal/filter.rs similarity index 100% rename from raphtory/src/db/api/view/internal/base_filter.rs rename to raphtory/src/db/api/view/internal/filter.rs diff --git a/raphtory/src/db/api/view/internal/mod.rs b/raphtory/src/db/api/view/internal/mod.rs index 2c15239959..82bc91b828 100644 --- a/raphtory/src/db/api/view/internal/mod.rs +++ b/raphtory/src/db/api/view/internal/mod.rs @@ -15,7 +15,7 @@ use std::{ sync::Arc, }; -mod base_filter; +mod filter; mod edge_filter_ops; mod filter_ops; mod into_dynamic; @@ -25,7 +25,7 @@ mod node_filter_ops; pub(crate) mod time_semantics; mod wrapped_graph; -pub use base_filter::*; +pub use filter::*; pub use edge_filter_ops::*; pub use filter_ops::*; pub use into_dynamic::{IntoDynHop, IntoDynamic}; diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 429e448257..3de1c0379e 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -3,11 +3,11 @@ use crate::{ db::{ api::{ state::{ - ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp, NodeTypeFilterOp}, + ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp}, Index, LazyNodeState, }, view::{ - internal::{InternalFilter, FilterOps, NodeList, Static}, + internal::{FilterOps, InternalFilter, NodeList, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, }, }, @@ -15,7 +15,7 @@ use crate::{ edges::NestedEdges, node::NodeView, path::PathFromGraph, - views::filter::{internal::CreateFilter, model::and_filter::AndOp}, + views::filter::internal::CreateFilter, }, }, errors::GraphError, @@ -30,6 +30,7 @@ use std::{ marker::PhantomData, sync::Arc, }; +use crate::db::api::state::ops::filter::{AndOp, NodeTypeFilterOp}; #[derive(Clone)] pub struct Nodes<'graph, G, GH = G, F = Const> { 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 0ba5365771..eac291ff4a 100644 --- a/raphtory/src/db/graph/views/filter/and_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/and_filtered_graph.rs @@ -12,7 +12,7 @@ use crate::{ }, graph::views::filter::{ internal::CreateFilter, - model::{and_filter::AndOp, AndFilter}, + model::AndFilter, }, }, errors::GraphError, @@ -29,6 +29,7 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; +use crate::db::api::state::ops::filter::AndOp; #[derive(Debug, Clone)] pub struct AndFilteredGraph { 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 index 4dc7a355db..e12dcecb1b 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -1,5 +1,5 @@ use crate::db::api::state::ops::NodeFilterOp; -use crate::db::api::view::internal::{GraphView, InternalNodeFilterOps}; +use crate::db::api::view::internal::{GraphView}; use crate::{ db::{ api::{ 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 88776d4fd5..dc2770e543 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,7 +3,6 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - state::ops::NotANodeFilter, view::internal::{ GraphView, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, @@ -24,6 +23,7 @@ use crate::{ }; use raphtory_api::{core::storage::timeindex::AsTime, inherit::Base}; use raphtory_storage::{core_ops::InheritCoreGraphOps, graph::edges::edge_ref::EdgeStorageRef}; +use crate::db::api::state::ops::NotANodeFilter; #[derive(Debug, Clone)] pub struct EdgePropertyFilteredGraph { @@ -196,20 +196,20 @@ mod test_edge_property_filtered_graph { vec!["John->David"] ); - // let g_expected = Graph::new(); - // g_expected - // .add_edge(1, "John", "David", [("band", "Dead & Company")], None) - // .unwrap(); - // - // assert_eq!( - // filtered_edges - // .edges() - // .iter() - // .map(|e| format!("{}->{}", e.src().name(), e.dst().name())) - // .collect::>(), - // vec!["John->David"] - // ); - // assert_graph_equal(&filtered_edges, &g_expected); + let g_expected = Graph::new(); + g_expected + .add_edge(1, "John", "David", [("band", "Dead & Company")], None) + .unwrap(); + + assert_eq!( + filtered_edges + .edges() + .iter() + .map(|e| format!("{}->{}", e.src().name(), e.dst().name())) + .collect::>(), + vec!["John->David"] + ); + assert_graph_equal(&filtered_edges, &g_expected); } #[test] 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 58a4d102b3..4b8d83c0b6 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,7 +3,6 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - state::ops::NotANodeFilter, view::internal::{ GraphView, Immutable, InheritEdgeFilterOps, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, @@ -33,6 +32,7 @@ use raphtory_api::{ inherit::Base, }; use raphtory_storage::core_ops::InheritCoreGraphOps; +use crate::db::api::state::ops::NotANodeFilter; #[derive(Debug, Clone)] pub struct ExplodedEdgePropertyFilteredGraph { diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index c37917c328..4b9e7f0f50 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1419,16 +1419,6 @@ pub(crate) mod test_filters { graph.add_edge(time, src, dst, props, edge_type).unwrap(); } - // graph.add_node(1, "1", vec![("p2", 6u64.into_prop()), ("p3", 1u64.into_prop())], Some("fire_nation")).unwrap(); - // graph.add_node(1, "2", vec![("p2", 6u64.into_prop()), ("p3", 1u64.into_prop())], Some("fire_nation")).unwrap(); - - // graph.add_node(1, "1", NO_PROPS, None).unwrap(); - // graph.add_node(1, "2", NO_PROPS, None).unwrap(); - // graph.add_node(1, "3", NO_PROPS, None).unwrap(); - // graph.add_node(1, "David Gilmour", NO_PROPS, None).unwrap(); - // graph.add_node(1, "John Mayer", NO_PROPS, None).unwrap(); - // graph.add_node(1, "Jimmy Page", NO_PROPS, None).unwrap(); - graph } @@ -1513,10 +1503,6 @@ pub(crate) mod test_filters { graph.add_edge(time, src, dst, props, edge_type).unwrap(); } - // graph.add_node(1, 1, NO_PROPS, None).unwrap(); - // graph.add_node(1, 2, NO_PROPS, None).unwrap(); - // graph.add_node(1, 3, NO_PROPS, None).unwrap(); - graph } @@ -1544,15 +1530,6 @@ pub(crate) mod test_filters { graph.add_edge(time, src, dst, NO_PROPS, edge_type).unwrap(); } - // graph.add_node(1, "London", NO_PROPS, None).unwrap(); - // graph.add_node(2, "Paris", NO_PROPS, None).unwrap(); - // graph.add_node(2, "One", NO_PROPS, None).unwrap(); - // graph.add_node(2, "Two", NO_PROPS, None).unwrap(); - // graph.add_node(2, "Three", NO_PROPS, None).unwrap(); - // graph.add_node(2, "David Gilmour", NO_PROPS, None).unwrap(); - // graph.add_node(2, "John Mayer", NO_PROPS, None).unwrap(); - // graph.add_node(2, "Jimmy Page", NO_PROPS, None).unwrap(); - graph } @@ -7717,13 +7694,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - // assert_search_edges_results( - // g, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -7742,13 +7719,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - // assert_search_edges_results( - // g, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -7763,13 +7740,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - // assert_search_edges_results( - // g, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); + assert_search_edges_results( + g, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -8290,13 +8267,13 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - // assert_search_edges_results( - // init_edges_graph_with_num_ids, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); + assert_search_edges_results( + init_edges_graph_with_num_ids, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); } #[test] @@ -10433,21 +10410,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::NonDiskOnly, ); - // let filter = filter.try_as_composite_edge_filter().unwrap(); - // assert_filter_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // vec![TestGraphVariants::Graph], - // ); - // assert_search_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::NonDiskOnly, - // ); + let filter = filter.try_as_composite_edge_filter().unwrap(); + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + vec![TestGraphVariants::Graph], + ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::NonDiskOnly, + ); let filter = EdgeFilter .property("p2") @@ -10503,21 +10480,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::NonDiskOnly, ); - // let filter = filter.try_as_composite_edge_filter().unwrap(); - // assert_filter_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // vec![TestGraphVariants::Graph], - // ); - // assert_search_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::NonDiskOnly, - // ); + let filter = filter.try_as_composite_edge_filter().unwrap(); + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + vec![TestGraphVariants::Graph], + ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::NonDiskOnly, + ); let filter = EdgeFilter::dst() .name() @@ -10538,21 +10515,21 @@ pub(crate) mod test_filters { &expected_results, TestVariants::All, ); - // let filter = filter.try_as_composite_edge_filter().unwrap(); - // assert_filter_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::EventOnly, - // ); - // assert_search_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); + let filter = filter.try_as_composite_edge_filter().unwrap(); + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::EventOnly, + ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); let filter = EdgeFilter::src() .name() 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 415146d91c..2696e0bb61 100644 --- a/raphtory/src/db/graph/views/filter/model/and_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/and_filter.rs @@ -7,12 +7,6 @@ use crate::{ }; use std::{fmt, fmt::Display}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AndOp { - pub(crate) left: L, - pub(crate) right: R, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct AndFilter { pub(crate) left: L, 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 c828abc246..78f6b614fa 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -4,10 +4,7 @@ use crate::db::graph::views::filter::model::node_filter::{ use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ - api::{ - state::ops::NotANodeFilter, - view::{internal::GraphView, BoxableGraphView}, - }, + api::view::{internal::GraphView, BoxableGraphView}, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, @@ -29,6 +26,7 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; +use crate::db::api::state::ops::NotANodeFilter; #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum 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 index 59ed6ab37d..ff521b98fb 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -1,9 +1,6 @@ use crate::{ db::{ - api::{ - state::ops::NotANodeFilter, - view::{internal::GraphView, BoxableGraphView}, - }, + api::view::{internal::GraphView, BoxableGraphView}, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, @@ -25,6 +22,7 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; +use crate::db::api::state::ops::NotANodeFilter; use crate::db::graph::views::filter::model::node_filter::{InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps}; use crate::db::graph::views::filter::model::Wrap; 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 e32501f190..cefa631163 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,11 +1,11 @@ -use crate::db::api::state::ops::filter::NodeNameFilterOp; +use crate::db::api::state::ops::filter::{AndOp, NodeNameFilterOp, NodeTypeFilterOp, NotOp, OrOp}; use crate::{ db::{ api::{ state::{ ops::{ filter::{MaskOp, NodeIdFilterOp}, - NodeTypeFilterOp, TypeId, + TypeId, }, NodeOp, }, @@ -14,9 +14,9 @@ use crate::{ graph::views::filter::{ internal::CreateFilter, model::{ - and_filter::AndOp, edge_filter::CompositeEdgeFilter, + edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, - not_filter::NotOp, or_filter::OrOp, property_filter::PropertyFilter, AndFilter, + property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, node_filtered_graph::NodeFilteredGraph, 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 227dd3f609..5fb66a434c 100644 --- a/raphtory/src/db/graph/views/filter/model/not_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/not_filter.rs @@ -7,9 +7,6 @@ use crate::{ }; use std::{fmt, fmt::Display}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct NotOp(pub(crate) T); - #[derive(Debug, Clone, PartialEq, Eq)] pub struct NotFilter(pub(crate) T); 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 8a75e17c07..7cb08da838 100644 --- a/raphtory/src/db/graph/views/filter/model/or_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/or_filter.rs @@ -7,12 +7,6 @@ use crate::{ }; use std::{fmt, fmt::Display}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OrOp { - pub(crate) left: L, - pub(crate) right: R, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct OrFilter { pub(crate) left: L, 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 9244f5b6ba..79c8fddd9f 100644 --- a/raphtory/src/db/graph/views/filter/not_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/not_filtered_graph.rs @@ -12,7 +12,7 @@ use crate::{ }, graph::views::filter::{ internal::CreateFilter, - model::not_filter::{NotFilter, NotOp}, + model::not_filter::NotFilter, }, }, errors::GraphError, @@ -29,6 +29,7 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; +use crate::db::api::state::ops::filter::NotOp; #[derive(Debug, Clone)] pub struct 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 69649b3140..193ef4dc78 100644 --- a/raphtory/src/db/graph/views/filter/or_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/or_filtered_graph.rs @@ -12,7 +12,7 @@ use crate::{ }, graph::views::filter::{ internal::CreateFilter, - model::or_filter::{OrFilter, OrOp}, + model::or_filter::OrFilter, }, }, errors::GraphError, @@ -29,6 +29,7 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; +use crate::db::api::state::ops::filter::OrOp; #[derive(Debug, Clone)] pub struct OrFilteredGraph { diff --git a/raphtory/src/python/filter/exploded_edge_filter_builder.rs b/raphtory/src/python/filter/exploded_edge_filter_builder.rs index 3076ba12a5..f0661e3d85 100644 --- a/raphtory/src/python/filter/exploded_edge_filter_builder.rs +++ b/raphtory/src/python/filter/exploded_edge_filter_builder.rs @@ -1,6 +1,6 @@ use crate::{ db::graph::views::filter::model::{ - exploded_edge_filter::{ExplodedEdgeEndpoint, ExplodedEdgeFilter, ExplodedEndpointWrapper}, + exploded_edge_filter::{ExplodedEdgeFilter, ExplodedEndpointWrapper}, node_filter::{ NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, NodeTypeFilterBuilder, }, diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index b67b6052a8..3e5e5b134b 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -367,8 +367,7 @@ where type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - let obj = PyFilterOps::wrap(self); - Bound::new(py, obj) + PyFilterOps::wrap(self).into_pyobject(py) } } diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index cb2ef65895..ee4c5688c8 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -9,8 +9,7 @@ use crate::{ state::{ ops, ops::{ - filter::NO_FILTER, Degree, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, - NodeTypeFilterOp, + filter::NO_FILTER, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, }, LazyNodeState, NodeStateOps, }, @@ -26,7 +25,6 @@ use crate::{ node::NodeView, nodes::Nodes, path::{PathFromGraph, PathFromNode}, - views::filter::model::and_filter::AndOp, }, }, errors::GraphError, @@ -67,6 +65,8 @@ use raphtory_api::core::{ use raphtory_storage::core_ops::CoreGraphOps; use rayon::{iter::IntoParallelIterator, prelude::*}; use std::collections::{HashMap, HashSet}; +use crate::db::api::state::ops::Degree; +use crate::db::api::state::ops::filter::{AndOp, NodeTypeFilterOp}; /// A node (or node) in the graph. #[pyclass(name = "Node", subclass, module = "raphtory", frozen)] diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 68e2b2ef4c..682831270f 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -233,7 +233,6 @@ impl<'a> EdgeFilterExecutor<'a> { CompositeEdgeFilter::Dst(node_filter) => { let nfe = NodeFilterExecutor::new(self.index); let nodes = nfe.filter_nodes(graph, node_filter, usize::MAX, 0)?; - println!("nodes {:?}", nodes); let mut edges: Vec> = nodes .into_iter() .flat_map(|n| n.in_edges().into_iter()) @@ -246,20 +245,11 @@ impl<'a> EdgeFilterExecutor<'a> { 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, - }; - + CompositeEdgeFilter::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)?; + self.filter_edges(&graph.window(start, end), &filter.inner, limit, offset)?; Ok(res .into_iter() .map(|x| EdgeView::new(graph.clone(), x.edge)) diff --git a/raphtory/src/search/node_filter_executor.rs b/raphtory/src/search/node_filter_executor.rs index 577d838ff8..a63650e094 100644 --- a/raphtory/src/search/node_filter_executor.rs +++ b/raphtory/src/search/node_filter_executor.rs @@ -45,7 +45,7 @@ impl<'a> NodeFilterExecutor<'a> { fn execute_filter_query( &self, - filter: impl CreateFilter + std::fmt::Display + std::fmt::Debug, + filter: impl CreateFilter, graph: &G, query: Box, reader: &IndexReader, @@ -55,9 +55,7 @@ impl<'a> NodeFilterExecutor<'a> { let searcher = reader.searcher(); let collector = UniqueEntityFilterCollector::new(fields::NODE_ID.to_string()); let node_ids = searcher.search(&query, &collector)?; - println!("node_ids: {:?}", node_ids); let nodes = self.resolve_nodes_from_node_ids(filter, graph, node_ids)?; - println!("resolved_nodes: {:?}", nodes); if offset == 0 && limit >= nodes.len() { Ok(nodes) @@ -68,7 +66,7 @@ impl<'a> NodeFilterExecutor<'a> { fn execute_filter_property_query( &self, - filter: impl CreateFilter + std::fmt::Display + std::fmt::Debug, + filter: impl CreateFilter, graph: &G, query: Box, reader: &IndexReader, @@ -219,7 +217,6 @@ impl<'a> NodeFilterExecutor<'a> { offset: usize, ) -> Result>, GraphError> { let (node_index, query) = self.query_builder.build_node_query(filter)?; - println!("query {:?}, filter {}", query, filter); let reader = get_reader(&node_index.entity_index.index)?; let results = match query { Some(query) => self.execute_filter_query( @@ -252,20 +249,11 @@ 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, - }; - + CompositeNodeFilter::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)?; + self.filter_nodes(&graph.window(start, end), &filter.inner, limit, offset)?; Ok(res .into_iter() .map(|x| NodeView::new_internal(graph.clone(), x.node)) @@ -339,7 +327,7 @@ impl<'a> NodeFilterExecutor<'a> { fn resolve_nodes_from_node_ids( &self, - filter: impl CreateFilter + std::fmt::Display + std::fmt::Debug, + filter: impl CreateFilter, graph: &G, node_ids: HashSet, ) -> Result>, GraphError> { From 51e97c3486d1f8310912d6ed94739ecb36a7d8f3 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Fri, 21 Nov 2025 09:29:53 +0100 Subject: [PATCH 38/42] fix infinite type recursion --- .../graph/views/filter/model/edge_filter.rs | 16 ++++---- .../filter/model/exploded_edge_filter.rs | 26 +++++++----- .../graph/views/filter/model/node_filter.rs | 40 ++++--------------- 3 files changed, 33 insertions(+), 49 deletions(-) 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 78f6b614fa..3166e2de07 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,7 +1,3 @@ -use crate::db::graph::views::filter::model::node_filter::{ - InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, -}; -use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::view::{internal::GraphView, BoxableGraphView}, @@ -11,13 +7,14 @@ use crate::{ model::{ exploded_edge_filter::CompositeExplodedEdgeFilter, node_filter::{ - CompositeNodeFilter, NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, - NodeTypeFilterBuilder, + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, NodeIdFilterBuilder, + NodeNameFilterBuilder, NodeTypeFilterBuilder, }, property_filter::{ InternalPropertyFilterBuilderOps, Op, PropertyFilter, PropertyRef, }, - AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, + AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, }, }, @@ -89,7 +86,10 @@ impl CreateFilter for CompositeEdgeFilter { ))) } CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeEdgeFilter::Windowed(i) => Ok(Arc::new(i.create_filter(graph)?)), + 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( 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 index ff521b98fb..47d16bbc43 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -1,19 +1,23 @@ use crate::{ db::{ - api::view::{internal::GraphView, BoxableGraphView}, + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, model::{ edge_filter::{CompositeEdgeFilter, Endpoint}, node_filter::{ - CompositeNodeFilter, NodeFilter, + CompositeNodeFilter, InternalNodeFilterBuilderOps, + InternalNodeIdFilterBuilderOps, NodeFilter, }, property_filter::{ InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, PropertyFilterBuilder, PropertyRef, }, - AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, + AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, }, }, @@ -22,9 +26,6 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; -use crate::db::api::state::ops::NotANodeFilter; -use crate::db::graph::views::filter::model::node_filter::{InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps}; -use crate::db::graph::views::filter::model::Wrap; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CompositeExplodedEdgeFilter { @@ -88,7 +89,10 @@ impl CreateFilter for CompositeExplodedEdgeFilter { ))) } Self::Property(p) => Ok(Arc::new(p.create_filter(graph)?)), - Self::Windowed(pw) => Ok(Arc::new(pw.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( @@ -242,7 +246,9 @@ where } } -impl InternalPropertyFilterBuilderOps for ExplodedEndpointWrapper { +impl InternalPropertyFilterBuilderOps + for ExplodedEndpointWrapper +{ type Marker = T::Marker; #[inline] fn property_ref(&self) -> PropertyRef { @@ -286,7 +292,9 @@ impl InternalNodeFilterBuilderOps for ExplodedE } } -impl InternalNodeIdFilterBuilderOps for ExplodedEndpointWrapper { +impl InternalNodeIdFilterBuilderOps + for ExplodedEndpointWrapper +{ fn field_name(&self) -> &'static str { self.inner.field_name() } 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 cefa631163..26604eaac9 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -176,7 +176,8 @@ 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>; @@ -184,36 +185,8 @@ impl CreateFilter for CompositeNodeFilter { 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)?)), - _ => { - unreachable!() - } - }, - CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_filter(graph)?)), - CompositeNodeFilter::Windowed(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::Not(filter) => { - let base = filter.deref().clone(); - Ok(Arc::new(NotFilter(base).create_filter(graph)?)) - } - } + let filter = self.create_node_filter(graph.clone())?; + Ok(NodeFilteredGraph::new(graph, filter)) } fn create_node_filter<'graph, G: GraphView + 'graph>( @@ -230,7 +203,10 @@ impl CreateFilter for CompositeNodeFilter { } }, CompositeNodeFilter::Property(i) => Ok(Arc::new(i.create_node_filter(graph)?)), - CompositeNodeFilter::Windowed(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())?, From dfa0d377fd4da2d3a1c091c0fd5b46acfe4dc9e5 Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:51:41 +0000 Subject: [PATCH 39/42] fmt, fix recursion issue in search --- raphtory-graphql/src/model/graph/edge.rs | 2 +- raphtory-graphql/src/model/graph/graph.rs | 4 +- raphtory-graphql/src/model/graph/nodes.rs | 2 +- .../src/model/graph/path_from_node.rs | 2 +- raphtory/src/db/api/state/lazy_node_state.rs | 3 +- raphtory/src/db/api/state/ops/filter.rs | 44 ++++--- raphtory/src/db/api/state/ops/mod.rs | 20 +-- raphtory/src/db/api/state/ops/node.rs | 14 +-- raphtory/src/db/api/view/graph.rs | 12 +- raphtory/src/db/api/view/internal/mod.rs | 4 +- raphtory/src/db/api/view/time.rs | 2 +- raphtory/src/db/graph/edge.rs | 2 +- raphtory/src/db/graph/edges.rs | 2 +- raphtory/src/db/graph/node.rs | 2 +- raphtory/src/db/graph/nodes.rs | 21 +--- raphtory/src/db/graph/path.rs | 2 +- .../graph/views/filter/and_filtered_graph.rs | 8 +- .../views/filter/edge_node_filtered_graph.rs | 5 +- .../filter/edge_property_filtered_graph.rs | 8 +- .../exploded_edge_node_filtered_graph.rs | 115 ++++++++++++++++++ .../filter/exploded_edge_property_filter.rs | 2 +- raphtory/src/db/graph/views/filter/mod.rs | 49 +++++--- .../graph/views/filter/model/edge_filter.rs | 7 +- .../filter/model/exploded_edge_filter.rs | 22 ++-- .../src/db/graph/views/filter/model/mod.rs | 57 +++++---- .../graph/views/filter/model/node_filter.rs | 54 ++++---- .../views/filter/model/property_filter.rs | 92 ++++++++------ .../graph/views/filter/not_filtered_graph.rs | 8 +- .../graph/views/filter/or_filtered_graph.rs | 8 +- raphtory/src/db/graph/views/window_graph.rs | 6 +- raphtory/src/db/task/edge/eval_edge.rs | 3 +- raphtory/src/db/task/node/eval_node.rs | 5 +- raphtory/src/python/graph/node.rs | 5 +- raphtory/src/python/graph/views/graph_view.rs | 6 +- raphtory/src/search/edge_filter_executor.rs | 7 +- .../search/exploded_edge_filter_executor.rs | 21 ++-- raphtory/src/search/node_filter_executor.rs | 7 +- raphtory/src/search/searcher.rs | 5 +- 38 files changed, 394 insertions(+), 244 deletions(-) create mode 100644 raphtory/src/db/graph/views/filter/exploded_edge_node_filtered_graph.rs diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index f5cc2e8b76..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::{Filter, 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/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index e93fd482cf..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::{ - Filter, DynamicGraph, IntoDynamic, Select, NodeViewOps, - SearchableGraphOps, StaticGraphViewOps, TimeOps, + DynamicGraph, Filter, IntoDynamic, NodeViewOps, SearchableGraphOps, Select, + StaticGraphViewOps, TimeOps, }, }, graph::{ diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index de3e1c282d..8b4a01606d 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -17,7 +17,7 @@ use raphtory::{ db::{ api::{ state::{ops::DynNodeFilter, Index}, - view::{Filter, DynamicGraph, Select}, + view::{DynamicGraph, Filter, Select}, }, graph::{ nodes::{IntoDynNodes, Nodes}, diff --git a/raphtory-graphql/src/model/graph/path_from_node.rs b/raphtory-graphql/src/model/graph/path_from_node.rs index da06280318..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::{Filter, DynamicGraph, Select}, + api::view::{DynamicGraph, Filter, Select}, graph::{path::PathFromNode, views::filter::model::node_filter::CompositeNodeFilter}, }, errors::GraphError, diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 869f03d9e9..93e0ce5699 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -305,7 +305,7 @@ mod test { db::api::{ state::{ lazy_node_state::LazyNodeState, - ops::node::NodeOp, + ops::{node::NodeOp, Degree}, }, view::IntoDynamic, }, @@ -314,7 +314,6 @@ mod test { use raphtory_api::core::{entities::VID, Direction}; use raphtory_storage::core_ops::CoreGraphOps; use std::sync::Arc; - use crate::db::api::state::ops::Degree; struct TestWrapper(Op); #[test] diff --git a/raphtory/src/db/api/state/ops/filter.rs b/raphtory/src/db/api/state/ops/filter.rs index cad09adcfd..2d7e8af853 100644 --- a/raphtory/src/db/api/state/ops/filter.rs +++ b/raphtory/src/db/api/state/ops/filter.rs @@ -1,23 +1,30 @@ -use crate::db::{ - api::state::{ - ops::{Const, IntoDynNodeOp}, - NodeOp, +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, + }, + }, }, - graph::views::filter::model::Filter, + 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 raphtory_api::core::entities::VID; -use raphtory_storage::graph::{graph::GraphStorage, nodes::node_storage_ops::NodeStorageOps}; use std::sync::Arc; -use raphtory_api::core::storage::arc_str::OptionAsStr; -use raphtory_storage::core_ops::CoreGraphOps; -use crate::db::api::state::ops::TypeId; -use crate::db::api::view::internal::GraphView; -use crate::db::graph::create_node_type_filter; -use crate::db::graph::views::filter::internal::CreateFilter; -use crate::db::graph::views::filter::model::node_filter::NodeFilter; -use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; -use crate::errors::GraphError; -use crate::prelude::{GraphViewOps, PropertyFilter}; #[derive(Clone, Debug)] pub struct Mask { @@ -160,7 +167,6 @@ where impl IntoDynNodeOp for OrOp where Self: NodeOp + 'static {} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct AndOp { pub(crate) left: L, @@ -218,4 +224,4 @@ mod test { let c = Const(true); assert!(!c.is_filtered()); } -} \ No newline at end of file +} diff --git a/raphtory/src/db/api/state/ops/mod.rs b/raphtory/src/db/api/state/ops/mod.rs index 6f77a8f071..86bbc71d0d 100644 --- a/raphtory/src/db/api/state/ops/mod.rs +++ b/raphtory/src/db/api/state/ops/mod.rs @@ -3,18 +3,18 @@ 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::Direction; -use raphtory_api::core::entities::VID; -use raphtory_storage::graph::graph::GraphStorage; -use raphtory_storage::graph::nodes::node_storage_ops::NodeStorageOps; -use raphtory_storage::layer_ops::InternalLayerOps; -use std::sync::Arc; -use std::ops::Deref; -use crate::db::api::view::internal::filtered_node::FilteredNodeStorageOps; -use crate::db::api::view::internal::{FilterOps, FilterState, GraphView}; +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; @@ -76,4 +76,4 @@ impl IntoDynNodeOp for Arc Arc> { self.clone() } -} \ No newline at end of file +} diff --git a/raphtory/src/db/api/state/ops/node.rs b/raphtory/src/db/api/state/ops/node.rs index 6d3b258ba1..9d832e414f 100644 --- a/raphtory/src/db/api/state/ops/node.rs +++ b/raphtory/src/db/api/state/ops/node.rs @@ -1,15 +1,13 @@ +use crate::db::api::state::ops::{ + filter::{AndOp, NotOp, OrOp}, + Map, +}; use raphtory_api::core::{ entities::{GID, VID}, - storage::arc_str::ArcStr - , -}; -use raphtory_storage::{ - core_ops::CoreGraphOps, - graph::graph::GraphStorage, + storage::arc_str::ArcStr, }; +use raphtory_storage::{core_ops::CoreGraphOps, graph::graph::GraphStorage}; use std::sync::Arc; -use crate::db::api::state::ops::filter::{AndOp, NotOp, OrOp}; -use crate::db::api::state::ops::Map; pub trait NodeFilterOp: NodeOp + Clone { fn is_filtered(&self) -> bool; diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 99038f2dc6..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, - }, + filter::{model::TryAsCompositeFilter, node_filtered_graph::NodeFilteredGraph}, node_subgraph::NodeSubgraph, valid_graph::ValidGraph, }, @@ -50,8 +49,6 @@ use raphtory_storage::{ use rayon::prelude::*; use rustc_hash::FxHashSet; use std::sync::{atomic::Ordering, Arc}; -use crate::db::api::state::ops::filter::NodeTypeFilterOp; -use crate::db::graph::views::filter::node_filtered_graph::NodeFilteredGraph; /// This trait GraphViewOps defines operations for accessing /// information about a graph. The trait has associated types @@ -446,7 +443,10 @@ impl<'graph, G: GraphView + 'graph> GraphViewOps<'graph> for G { &self, node_types: I, ) -> NodeFilteredGraph { - NodeFilteredGraph::new(self.clone(), NodeTypeFilterOp::new_from_values(node_types, self)) + NodeFilteredGraph::new( + self.clone(), + NodeTypeFilterOp::new_from_values(node_types, self), + ) } fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { diff --git a/raphtory/src/db/api/view/internal/mod.rs b/raphtory/src/db/api/view/internal/mod.rs index 82bc91b828..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 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 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/time.rs b/raphtory/src/db/api/view/time.rs index 663405e122..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::{InternalFilter, GraphTimeSemanticsOps, InternalMaterialize}, + internal::{GraphTimeSemanticsOps, InternalFilter, InternalMaterialize}, time::internal::InternalTimeOps, }, }; diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs index 17a24bc408..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::{EdgeTimeSemanticsOps, InternalFilter, GraphView, Static}, + internal::{EdgeTimeSemanticsOps, GraphView, InternalFilter, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, diff --git a/raphtory/src/db/graph/edges.rs b/raphtory/src/db/graph/edges.rs index 5676a5b619..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::{InternalFilter, FilterOps, InternalSelect, Static}, + internal::{FilterOps, InternalFilter, InternalSelect, Static}, BaseEdgeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index 1b4e8732f5..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::{InternalFilter, GraphTimeSemanticsOps, NodeTimeSemanticsOps, Static}, + internal::{GraphTimeSemanticsOps, InternalFilter, NodeTimeSemanticsOps, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 3de1c0379e..d100cd1684 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -3,7 +3,10 @@ use crate::{ db::{ api::{ state::{ - ops::{Const, IntoDynNodeOp, NodeFilterOp, NodeOp}, + ops::{ + filter::{AndOp, NodeTypeFilterOp}, + Const, IntoDynNodeOp, NodeFilterOp, NodeOp, + }, Index, LazyNodeState, }, view::{ @@ -12,9 +15,7 @@ use crate::{ }, }, graph::{ - edges::NestedEdges, - node::NodeView, - path::PathFromGraph, + edges::NestedEdges, node::NodeView, path::PathFromGraph, views::filter::internal::CreateFilter, }, }, @@ -30,7 +31,6 @@ use std::{ marker::PhantomData, sync::Arc, }; -use crate::db::api::state::ops::filter::{AndOp, NodeTypeFilterOp}; #[derive(Clone)] pub struct Nodes<'graph, G, GH = G, F = Const> { @@ -120,17 +120,6 @@ impl } } -impl - From> - for Nodes<'static, DynamicGraph, DynamicGraph, Arc>> -{ - fn from( - value: Nodes<'static, G, GH, F>, - ) -> Nodes<'static, DynamicGraph, DynamicGraph, Arc>> { - value.into_dyn() - } -} - impl<'graph, G> Nodes<'graph, G> where G: GraphViewOps<'graph> + Clone, diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index 482895c1fa..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::{InternalFilter, FilterOps, InternalSelect, Static}, + internal::{FilterOps, InternalFilter, InternalSelect, Static}, BaseNodeViewOps, BoxedLIter, DynamicGraph, IntoDynBoxed, IntoDynamic, StaticGraphViewOps, }, 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 eac291ff4a..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,7 +2,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - state::ops::NodeFilterOp, + state::ops::{filter::AndOp, NodeFilterOp}, view::internal::{ EdgeList, GraphView, Immutable, InheritMaterialize, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, InternalEdgeLayerFilterOps, @@ -10,10 +10,7 @@ use crate::{ NodeList, Static, }, }, - graph::views::filter::{ - internal::CreateFilter, - model::AndFilter, - }, + graph::views::filter::{internal::CreateFilter, model::AndFilter}, }, errors::GraphError, prelude::GraphViewOps, @@ -29,7 +26,6 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; -use crate::db::api::state::ops::filter::AndOp; #[derive(Debug, Clone)] pub struct AndFilteredGraph { 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 index e12dcecb1b..c1937c58b2 100644 --- a/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/edge_node_filtered_graph.rs @@ -1,11 +1,10 @@ -use crate::db::api::state::ops::NodeFilterOp; -use crate::db::api::view::internal::{GraphView}; use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::NodeFilterOp, view::internal::{ - Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, + GraphView, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, Static, 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 dc2770e543..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,6 +3,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::NotANodeFilter, view::internal::{ GraphView, Immutable, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritExplodedEdgeFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, @@ -23,7 +24,6 @@ use crate::{ }; use raphtory_api::{core::storage::timeindex::AsTime, inherit::Base}; use raphtory_storage::{core_ops::InheritCoreGraphOps, graph::edges::edge_ref::EdgeStorageRef}; -use crate::db::api::state::ops::NotANodeFilter; #[derive(Debug, Clone)] pub struct EdgePropertyFilteredGraph { @@ -154,8 +154,9 @@ mod test_edge_property_filtered_graph { views::{ deletion_graph::PersistentGraph, filter::model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, - ComposableFilter, PropertyFilterFactory, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, ComposableFilter, + PropertyFilterFactory, }, }, }, @@ -169,7 +170,6 @@ mod test_edge_property_filtered_graph { use proptest::{arbitrary::any, proptest}; use raphtory_api::core::entities::properties::prop::PropType; use raphtory_storage::mutation::addition_ops::InternalAdditionOps; - use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; #[test] fn test_edge_filter2() { 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 4b8d83c0b6..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,6 +3,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, + state::ops::NotANodeFilter, view::internal::{ GraphView, Immutable, InheritEdgeFilterOps, InheritEdgeHistoryFilter, InheritEdgeLayerFilterOps, InheritLayerOps, InheritListOps, InheritMaterialize, @@ -32,7 +33,6 @@ use raphtory_api::{ inherit::Base, }; use raphtory_storage::core_ops::InheritCoreGraphOps; -use crate::db::api::state::ops::NotANodeFilter; #[derive(Debug, Clone)] pub struct ExplodedEdgePropertyFilteredGraph { diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index 4b9e7f0f50..fe5f16bbb5 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -1,6 +1,7 @@ pub mod and_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; @@ -2289,16 +2290,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::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, - ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, + 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; @@ -8822,18 +8828,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::{ElemQualifierOps, ListAggOps, PropertyFilterOps}, - ComposableFilter, PropertyFilterFactory, TemporalPropertyFilterFactory, + 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; 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 3166e2de07..a416796bba 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -1,6 +1,9 @@ use crate::{ db::{ - api::view::{internal::GraphView, BoxableGraphView}, + api::{ + state::ops::NotANodeFilter, + view::{internal::GraphView, BoxableGraphView}, + }, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, internal::CreateFilter, @@ -23,7 +26,6 @@ use crate::{ }; use raphtory_core::utils::time::IntoTime; use std::{fmt, fmt::Display, sync::Arc}; -use crate::db::api::state::ops::NotANodeFilter; #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Endpoint { @@ -221,6 +223,7 @@ impl InternalPropertyFilterBuilderOps for E } impl InternalNodeFilterBuilderOps for EndpointWrapper { + type FilterType = T::FilterType; fn field_name(&self) -> &'static str { self.inner.field_name() } 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 index 47d16bbc43..022c7d32ae 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -6,6 +6,7 @@ use crate::{ }, graph::views::filter::{ edge_node_filtered_graph::EdgeNodeFilteredGraph, + exploded_edge_node_filtered_graph::ExplodedEdgeNodeFilteredGraph, internal::CreateFilter, model::{ edge_filter::{CompositeEdgeFilter, Endpoint}, @@ -186,14 +187,13 @@ where } } -impl CreateFilter for ExplodedEndpointWrapper -where - T: TryAsCompositeFilter + Clone + 'static, -{ +impl CreateFilter for ExplodedEndpointWrapper { type EntityFiltered<'graph, G: GraphViewOps<'graph>> - = Arc + = ExplodedEdgeNodeFilteredGraph> where - T: 'graph; + Self: 'graph, + G: GraphViewOps<'graph>; + type NodeFilter<'graph, G> = NotANodeFilter where @@ -207,8 +207,12 @@ where where T: 'graph, { - self.try_as_composite_exploded_edge_filter()? - .create_filter(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>( @@ -287,6 +291,8 @@ impl ExplodedEdgeFilter { } impl InternalNodeFilterBuilderOps for ExplodedEndpointWrapper { + type FilterType = T::FilterType; + fn field_name(&self) -> &'static str { self.inner.field_name() } diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 91b933d92f..215c4de872 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -1,32 +1,38 @@ -use crate::db::api::view::internal::GraphView; -use crate::db::graph::views::filter::internal::CreateFilter; pub(crate) use crate::db::graph::views::filter::model::and_filter::AndFilter; -use crate::db::graph::views::filter::model::exploded_edge_filter::ExplodedEndpointWrapper; -use crate::db::graph::views::filter::model::node_filter::{ - InternalNodeFilterBuilderOps, InternalNodeIdFilterBuilderOps, -}; -use crate::db::graph::views::filter::model::property_filter::{ - CombinedFilter, InternalPropertyFilterBuilderOps, OpChainBuilder, PropertyFilterOps, - PropertyRef, -}; -use crate::db::graph::views::window_graph::WindowedGraph; -use crate::prelude::{GraphViewOps, TimeOps}; use crate::{ - db::graph::views::filter::model::{ - edge_filter::{CompositeEdgeFilter, EdgeFilter, EndpointWrapper}, - exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, - filter_operator::FilterOperator, - node_filter::{CompositeNodeFilter, NodeFilter, NodeNameFilter, NodeTypeFilter}, - not_filter::NotFilter, - or_filter::OrFilter, - property_filter::{MetadataFilterBuilder, PropertyFilter, PropertyFilterBuilder}, + 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, + }, }, errors::GraphError, + prelude::{GraphViewOps, TimeOps}, }; -use raphtory_api::core::storage::timeindex::AsTime; use raphtory_api::core::{ entities::{GidRef, GID}, - storage::timeindex::TimeIndexEntry, + storage::timeindex::{AsTime, TimeIndexEntry}, }; use raphtory_core::utils::time::IntoTime; use std::{collections::HashSet, fmt, fmt::Display, ops::Deref, sync::Arc}; @@ -300,6 +306,8 @@ impl Filter { } impl InternalNodeFilterBuilderOps for Windowed { + type FilterType = T::FilterType; + fn field_name(&self) -> &'static str { self.inner.field_name() } @@ -318,6 +326,10 @@ impl InternalPropertyFilterBuilderOps for W self.inner.property_ref() } + fn ops(&self) -> &[Op] { + self.inner.ops() + } + fn entity(&self) -> Self::Marker { self.inner.entity() } @@ -433,6 +445,7 @@ impl ComposableFilter for EndpointWrapper where T: TryAsCompositeFilter + impl ComposableFilter for AndFilter {} impl ComposableFilter for OrFilter {} impl ComposableFilter for NotFilter {} +impl ComposableFilter for Windowed {} trait EntityMarker: Clone + Send + Sync {} 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 26604eaac9..bc30941742 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -1,10 +1,12 @@ -use crate::db::api::state::ops::filter::{AndOp, NodeNameFilterOp, NodeTypeFilterOp, NotOp, OrOp}; use crate::{ db::{ api::{ state::{ ops::{ - filter::{MaskOp, NodeIdFilterOp}, + filter::{ + AndOp, MaskOp, NodeIdFilterOp, NodeNameFilterOp, NodeTypeFilterOp, NotOp, + OrOp, + }, TypeId, }, NodeOp, @@ -16,8 +18,8 @@ use crate::{ model::{ edge_filter::CompositeEdgeFilter, exploded_edge_filter::CompositeExplodedEdgeFilter, filter_operator::FilterOperator, - property_filter::PropertyFilter, AndFilter, - Filter, FilterValue, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, + property_filter::PropertyFilter, AndFilter, Filter, FilterValue, NotFilter, + OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, node_filtered_graph::NodeFilteredGraph, }, @@ -239,54 +241,59 @@ impl TryAsCompositeFilter for CompositeNodeFilter { } pub trait InternalNodeFilterBuilderOps: Send + Sync + Wrap { + type FilterType: From; fn field_name(&self) -> &'static str; } impl InternalNodeFilterBuilderOps for Arc { + 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::Wrapped { + fn eq(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::eq(self.field_name(), value); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn ne(&self, value: impl Into) -> Self::Wrapped { + fn ne(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::ne(self.field_name(), value); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn is_in(&self, values: impl IntoIterator) -> Self::Wrapped { + fn is_in(&self, values: impl IntoIterator) -> Self::Wrapped { let filter = Filter::is_in(self.field_name(), values); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn is_not_in(&self, values: impl IntoIterator) -> Self::Wrapped { + fn is_not_in( + &self, + values: impl IntoIterator, + ) -> Self::Wrapped { let filter = Filter::is_not_in(self.field_name(), values); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn starts_with(&self, value: impl Into) -> Self::Wrapped { + fn starts_with(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::starts_with(self.field_name(), value); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn ends_with(&self, value: impl Into) -> Self::Wrapped { + fn ends_with(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::ends_with(self.field_name(), value); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn contains(&self, value: impl Into) -> Self::Wrapped { + fn contains(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::contains(self.field_name(), value); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } - fn not_contains(&self, value: impl Into) -> Self::Wrapped { + fn not_contains(&self, value: impl Into) -> Self::Wrapped { let filter = Filter::not_contains(self.field_name(), value.into()); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } fn fuzzy_search( @@ -294,10 +301,10 @@ pub trait NodeFilterBuilderOps: InternalNodeFilterBuilderOps { value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> Self::Wrapped { + ) -> Self::Wrapped { let filter = Filter::fuzzy_search(self.field_name(), value, levenshtein_distance, prefix_match); - self.wrap(NodeNameFilter(filter)) + self.wrap(filter.into()) } } @@ -418,6 +425,8 @@ impl InternalNodeIdFilterBuilderOps for NodeIdFilterBuilder { pub struct NodeNameFilterBuilder; impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { + type FilterType = NodeNameFilter; + fn field_name(&self) -> &'static str { "node_name" } @@ -435,6 +444,7 @@ impl Wrap for NodeNameFilterBuilder { pub struct NodeTypeFilterBuilder; impl InternalNodeFilterBuilderOps for NodeTypeFilterBuilder { + type FilterType = NodeTypeFilter; fn field_name(&self) -> &'static str { "node_type" } 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 383a251f6f..bc68380506 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -1,5 +1,3 @@ -use crate::db::graph::views::filter::internal::CreateFilter; -use crate::db::graph::views::filter::model::Wrap; use crate::{ db::{ api::{ @@ -13,12 +11,15 @@ use crate::{ graph::{ edge::EdgeView, node::NodeView, - views::filter::model::{ - edge_filter::{CompositeEdgeFilter, EdgeFilter}, - exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, - filter_operator::FilterOperator, - node_filter::{CompositeNodeFilter, NodeFilter}, - TryAsCompositeFilter, + views::filter::{ + internal::CreateFilter, + model::{ + edge_filter::{CompositeEdgeFilter, EdgeFilter}, + exploded_edge_filter::{CompositeExplodedEdgeFilter, ExplodedEdgeFilter}, + filter_operator::FilterOperator, + node_filter::{CompositeNodeFilter, NodeFilter}, + TryAsCompositeFilter, Wrap, + }, }, }, }, @@ -1354,9 +1355,7 @@ pub trait InternalPropertyFilterBuilderOps: Send + Sync + Wrap { fn property_ref(&self) -> PropertyRef; - fn ops(&self) -> &[Op] { - &[] - } + fn ops(&self) -> &[Op]; fn entity(&self) -> Self::Marker; } @@ -1615,6 +1614,10 @@ where PropertyRef::Property(self.0.clone()) } + fn ops(&self) -> &[Op] { + &[] + } + fn entity(&self) -> Self::Marker { self.1.clone() } @@ -1648,6 +1651,10 @@ where PropertyRef::Metadata(self.0.clone()) } + fn ops(&self) -> &[Op] { + &[] + } + fn entity(&self) -> Self::Marker { self.1.clone() } @@ -1736,26 +1743,28 @@ where } pub trait ElemQualifierOps: InternalPropertyFilterBuilderOps { - fn any(&self) -> OpChainBuilder + fn any(&self) -> Self::Wrapped> 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.wrap(builder) } - fn all(&self) -> OpChainBuilder + fn all(&self) -> Self::Wrapped> 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.wrap(builder) } } @@ -1772,60 +1781,67 @@ impl PropertyFilterBuilder { } pub trait ListAggOps: InternalPropertyFilterBuilderOps + Sized { - fn len(&self) -> OpChainBuilder { - OpChainBuilder { + fn len(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Len]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn sum(&self) -> OpChainBuilder { - OpChainBuilder { + fn sum(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Sum]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn avg(&self) -> OpChainBuilder { - OpChainBuilder { + fn avg(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Avg]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn min(&self) -> OpChainBuilder { - OpChainBuilder { + fn min(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Min]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn max(&self) -> OpChainBuilder { - OpChainBuilder { + fn max(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Max]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn first(&self) -> OpChainBuilder { - OpChainBuilder { + fn first(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::First]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } - fn last(&self) -> OpChainBuilder { - OpChainBuilder { + fn last(&self) -> Self::Wrapped> { + let builder = OpChainBuilder { prop_ref: self.property_ref(), ops: self.ops().iter().copied().chain([Op::Last]).collect(), entity: self.entity(), - } + }; + self.wrap(builder) } } 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 79c8fddd9f..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,7 +2,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - state::ops::NodeFilterOp, + state::ops::{filter::NotOp, NodeFilterOp}, view::internal::{ FilterOps, GraphView, Immutable, InheritEdgeHistoryFilter, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, InheritStorageOps, @@ -10,10 +10,7 @@ use crate::{ InternalExplodedEdgeFilterOps, InternalNodeFilterOps, Static, }, }, - graph::views::filter::{ - internal::CreateFilter, - model::not_filter::NotFilter, - }, + graph::views::filter::{internal::CreateFilter, model::not_filter::NotFilter}, }, errors::GraphError, prelude::GraphViewOps, @@ -29,7 +26,6 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; -use crate::db::api::state::ops::filter::NotOp; #[derive(Debug, Clone)] pub struct 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 193ef4dc78..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,7 +2,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - state::ops::NodeFilterOp, + state::ops::{filter::OrOp, NodeFilterOp}, view::internal::{ GraphView, Immutable, InheritLayerOps, InheritListOps, InheritMaterialize, InheritStorageOps, InheritTimeSemantics, InternalEdgeFilterOps, @@ -10,10 +10,7 @@ use crate::{ Static, }, }, - graph::views::filter::{ - internal::CreateFilter, - model::or_filter::OrFilter, - }, + graph::views::filter::{internal::CreateFilter, model::or_filter::OrFilter}, }, errors::GraphError, prelude::GraphViewOps, @@ -29,7 +26,6 @@ use raphtory_storage::{ core_ops::InheritCoreGraphOps, graph::{edges::edge_ref::EdgeStorageRef, nodes::node_ref::NodeStorageRef}, }; -use crate::db::api::state::ops::filter::OrOp; #[derive(Debug, Clone)] pub struct OrFilteredGraph { diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 40b4c9a839..a336fede7b 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -3299,8 +3299,9 @@ mod views_test { TestGraphVariants, TestVariants, WindowGraphTransformer, }, views::filter::model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, - ComposableFilter, PropertyFilterFactory, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, ComposableFilter, + PropertyFilterFactory, }, }, }, @@ -3311,7 +3312,6 @@ mod views_test { }; use raphtory_api::core::{entities::properties::prop::Prop, storage::arc_str::ArcStr}; use std::sync::Arc; - use crate::db::graph::views::filter::model::node_filter::NodeFilterBuilderOps; fn init_graph( graph: G, diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index 6d3dd7968e..ca0a1885e1 100644 --- a/raphtory/src/db/task/edge/eval_edge.rs +++ b/raphtory/src/db/task/edge/eval_edge.rs @@ -137,7 +137,8 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S, CS: ComputeState + 'a> Clon } } -impl<'graph, 'a: 'graph, Current, S, CS> InternalFilter<'graph> for EvalEdgeView<'graph, 'a, Current, CS, S> +impl<'graph, 'a: 'graph, Current, S, CS> InternalFilter<'graph> + for EvalEdgeView<'graph, 'a, Current, CS, S> where 'a: 'graph, Current: GraphViewOps<'graph>, diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs index 7ac71005df..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::{InternalFilter, GraphView}, + internal::{GraphView, InternalFilter}, BaseNodeViewOps, BoxedLIter, IntoDynBoxed, }, }, @@ -358,7 +358,8 @@ impl<'graph, 'a: 'graph, G: GraphViewOps<'graph>, S: 'static, CS: ComputeState + } } -impl<'graph, 'a, S, CS, Current> InternalFilter<'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>, diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index ee4c5688c8..a5ccefd3e6 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -9,7 +9,8 @@ use crate::{ state::{ ops, ops::{ - filter::NO_FILTER, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, + filter::{AndOp, NodeTypeFilterOp, NO_FILTER}, + Degree, DynNodeFilter, IntoDynNodeOp, NodeFilterOp, }, LazyNodeState, NodeStateOps, }, @@ -65,8 +66,6 @@ use raphtory_api::core::{ use raphtory_storage::core_ops::CoreGraphOps; use rayon::{iter::IntoParallelIterator, prelude::*}; use std::collections::{HashMap, HashSet}; -use crate::db::api::state::ops::Degree; -use crate::db::api::state::ops::filter::{AndOp, NodeTypeFilterOp}; /// A node (or node) in the graph. #[pyclass(name = "Node", subclass, module = "raphtory", frozen)] diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index fefbd9d569..7e5c8a938d 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::NodePropertyFilterOp, view::{ filter_ops::Filter, - internal::{DynamicGraph, InternalFilter, IntoDynHop, IntoDynamic, MaterializedGraph}, + internal::{ + DynamicGraph, InternalFilter, IntoDynHop, IntoDynamic, MaterializedGraph, + }, LayerOps, StaticGraphViewOps, }, }, @@ -43,7 +46,6 @@ use pyo3::prelude::*; use raphtory_api::core::storage::arc_str::ArcStr; use rayon::prelude::*; use std::collections::HashMap; -use crate::db::api::state::ops::filter::NodePropertyFilterOp; impl<'py> IntoPyObject<'py> for MaterializedGraph { type Target = PyAny; diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index 682831270f..5dcbdf7108 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{internal::FilterOps, Filter, StaticGraphViewOps}, + api::view::{internal::FilterOps, BoxableGraphView, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ @@ -248,8 +248,9 @@ impl<'a> EdgeFilterExecutor<'a> { CompositeEdgeFilter::Windowed(filter) => { let start = filter.start.t(); let end = filter.end.t(); - let res = - self.filter_edges(&graph.window(start, end), &filter.inner, limit, offset)?; + 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)) diff --git a/raphtory/src/search/exploded_edge_filter_executor.rs b/raphtory/src/search/exploded_edge_filter_executor.rs index 2f0de0d6c6..44b6535f8b 100644 --- a/raphtory/src/search/exploded_edge_filter_executor.rs +++ b/raphtory/src/search/exploded_edge_filter_executor.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::{internal::FilterOps, Filter, StaticGraphViewOps}, + api::view::{internal::FilterOps, BoxableGraphView, Filter, StaticGraphViewOps}, graph::{ edge::EdgeView, views::filter::{ @@ -229,20 +229,13 @@ impl<'a> ExplodedEdgeFilterExecutor<'a> { 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(); + CompositeExplodedEdgeFilter::Windowed(filter) => { + let start = filter.start.t(); + let end = filter.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, - }; - - 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/node_filter_executor.rs b/raphtory/src/search/node_filter_executor.rs index a63650e094..1086b194aa 100644 --- a/raphtory/src/search/node_filter_executor.rs +++ b/raphtory/src/search/node_filter_executor.rs @@ -1,6 +1,6 @@ use crate::{ db::{ - api::view::StaticGraphViewOps, + api::view::{BoxableGraphView, StaticGraphViewOps}, graph::{ node::NodeView, views::filter::{ @@ -252,8 +252,9 @@ impl<'a> NodeFilterExecutor<'a> { CompositeNodeFilter::Windowed(filter) => { let start = filter.start.t(); let end = filter.end.t(); - let res = - self.filter_nodes(&graph.window(start, end), &filter.inner, limit, offset)?; + 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)) diff --git a/raphtory/src/search/searcher.rs b/raphtory/src/search/searcher.rs index b96122f8a3..4344debf17 100644 --- a/raphtory/src/search/searcher.rs +++ b/raphtory/src/search/searcher.rs @@ -182,8 +182,9 @@ mod search_tests { db::{ api::view::SearchableGraphOps, graph::views::filter::model::{ - edge_filter::EdgeFilter, property_filter::PropertyFilterOps, - PropertyFilterFactory, TryAsCompositeFilter, + edge_filter::EdgeFilter, node_filter::NodeFilterBuilderOps, + property_filter::PropertyFilterOps, PropertyFilterFactory, + TryAsCompositeFilter, }, }, prelude::{AdditionOps, EdgeViewOps, Graph, IndexMutationOps, NodeViewOps, NO_PROPS}, From 0edc36f1cd6b354d990db01a67a39789bf33834c Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:49:02 +0000 Subject: [PATCH 40/42] impl py --- raphtory/src/db/graph/views/filter/mod.rs | 3 +- .../views/filter/model/property_filter.rs | 1 + .../graph/views/filter/node_filtered_graph.rs | 44 ++- .../src/python/filter/edge_filter_builders.rs | 48 +-- .../filter/exploded_edge_filter_builder.rs | 274 +--------------- raphtory/src/python/filter/mod.rs | 8 +- .../src/python/filter/node_filter_builders.rs | 299 +++++++++--------- .../python/filter/property_filter_builders.rs | 20 +- raphtory/src/python/filter/window_filter.rs | 35 +- raphtory/src/python/graph/views/graph_view.rs | 30 +- .../types/macros/trait_impl/filter_ops.rs | 6 +- 11 files changed, 239 insertions(+), 529 deletions(-) diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index fe5f16bbb5..c3198c4bd7 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -140,7 +140,8 @@ pub(crate) mod test_filters { }, views::filter::{ model::{ - node_filter::NodeFilter, property_filter::PropertyFilterOps, + node_filter::NodeFilter, + property_filter::{ListAggOps, PropertyFilterOps}, PropertyFilterFactory, }, test_filters::IdentityGraphTransformer, 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 bc68380506..c6f05d90ed 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -1669,6 +1669,7 @@ pub struct OpChainBuilder { impl Wrap for OpChainBuilder { type Wrapped = T; + fn wrap(&self, value: T) -> Self::Wrapped { value } diff --git a/raphtory/src/db/graph/views/filter/node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs index fd92b363a7..58e195c5f8 100644 --- a/raphtory/src/db/graph/views/filter/node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs @@ -39,16 +39,40 @@ impl Base for NodeFilteredGraph { impl Static for NodeFilteredGraph {} impl Immutable for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritCoreGraphOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritStorageOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritLayerOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritListOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritMaterialize for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritAllEdgeFilterOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritPropertiesOps for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritTimeSemantics for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritNodeHistoryFilter for NodeFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>, F> InheritEdgeHistoryFilter 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 diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index 8fcee6c1e4..a4a1a9d815 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -2,12 +2,13 @@ use crate::{ db::graph::views::filter::model::{ edge_filter::{EdgeFilter, EndpointWrapper}, node_filter::{ - NodeFilter, NodeIdFilterBuilder, NodeIdFilterBuilderOps, NodeNameFilterBuilder, - NodeTypeFilterBuilder, + NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, NodeIdFilterBuilderOps, + NodeNameFilterBuilder, NodeTypeFilterBuilder, }, - property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, + property_filter::{MetadataFilterBuilder, PropertyFilterBuilder, PropertyFilterOps}, PropertyFilterFactory, Windowed, }, + prelude::TimeOps, python::{ filter::{ filter_expr::PyFilterExpr, @@ -213,7 +214,7 @@ impl PyEdgeFilterOp { #[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeEndpoint(pub EdgeEndpoint); +pub struct PyEdgeEndpoint(pub EndpointWrapper); #[pymethods] impl PyEdgeEndpoint { @@ -228,24 +229,6 @@ impl PyEdgeEndpoint { fn node_type(&self) -> PyEdgeFilterOp { PyEdgeFilterOp::from(self.0.node_type()) } - - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder> = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = self.0.metadata(name); - b.into_pyobject(py) - } - - fn window(&self, py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyEdgeEndpointWindow(self.0.window(py_start, py_end))) - } } #[pyclass(frozen, name = "Edge", module = "raphtory.filter")] @@ -286,24 +269,3 @@ impl PyEdgeFilter { Ok(PyEdgeWindow(Windowed::from_times(start, end, EdgeFilter))) } } - -#[pyclass(frozen, name = "EdgeEndpointWindow", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyEdgeEndpointWindow(pub EndpointWrapper>); - -#[pymethods] -impl PyEdgeEndpointWindow { - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder<_> = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder<_> = self.0.metadata(name); - b.into_pyobject(py) - } -} diff --git a/raphtory/src/python/filter/exploded_edge_filter_builder.rs b/raphtory/src/python/filter/exploded_edge_filter_builder.rs index f0661e3d85..baf2d94ce2 100644 --- a/raphtory/src/python/filter/exploded_edge_filter_builder.rs +++ b/raphtory/src/python/filter/exploded_edge_filter_builder.rs @@ -1,253 +1,18 @@ use crate::{ db::graph::views::filter::model::{ - exploded_edge_filter::{ExplodedEdgeFilter, ExplodedEndpointWrapper}, - node_filter::{ - NodeFilter, NodeIdFilterBuilder, NodeNameFilterBuilder, NodeTypeFilterBuilder, - }, + exploded_edge_filter::ExplodedEdgeFilter, property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, PropertyFilterFactory, Windowed, }, python::{ filter::{ - filter_expr::PyFilterExpr, property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, window_filter::PyExplodedEdgeWindow, }, - types::iterable::FromIterable, utils::PyTime, }, }; use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyResult, Python}; -use raphtory_api::core::entities::GID; -use std::sync::Arc; - -#[pyclass(frozen, name = "ExplodedEdgeIdFilterOp", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyExplodedEdgeIdFilterOp(pub ExplodedEndpointWrapper); - -#[pymethods] -impl PyExplodedEdgeIdFilterOp { - fn __eq__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.eq(value))) - } - - fn __ne__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ne(value))) - } - - fn __lt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.lt(value))) - } - - fn __le__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.le(value))) - } - - fn __gt__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.gt(value))) - } - - fn __ge__(&self, value: GID) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ge(value))) - } - - fn is_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_in(values))) - } - - fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.is_not_in(values))) - } - - fn starts_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.starts_with(value))) - } - - fn ends_with(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.ends_with(value))) - } - - fn contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.contains(value))) - } - - fn not_contains(&self, value: String) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.not_contains(value))) - } - - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - PyFilterExpr(Arc::new(self.0.fuzzy_search( - value, - levenshtein_distance, - prefix_match, - ))) - } -} - -#[pyclass(frozen, name = "ExplodedEdgeFilterOp", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyExplodedEdgeFilterOp(ExplodedEdgeTextBuilder); - -#[derive(Clone)] -enum ExplodedEdgeTextBuilder { - Name(ExplodedEndpointWrapper), - Type(ExplodedEndpointWrapper), -} - -impl From> for PyExplodedEdgeFilterOp { - fn from(v: ExplodedEndpointWrapper) -> Self { - PyExplodedEdgeFilterOp(ExplodedEdgeTextBuilder::Name(v)) - } -} - -impl From> for PyExplodedEdgeFilterOp { - fn from(v: ExplodedEndpointWrapper) -> Self { - PyExplodedEdgeFilterOp(ExplodedEdgeTextBuilder::Type(v)) - } -} - -impl PyExplodedEdgeFilterOp { - #[inline] - fn map( - &self, - f_name: impl FnOnce(&ExplodedEndpointWrapper) -> T, - f_type: impl FnOnce(&ExplodedEndpointWrapper) -> T, - ) -> T { - match &self.0 { - ExplodedEdgeTextBuilder::Name(n) => f_name(n), - ExplodedEdgeTextBuilder::Type(t) => f_type(t), - } - } -} - -#[pymethods] -impl PyExplodedEdgeFilterOp { - fn __eq__(&self, value: String) -> PyFilterExpr { - self.map( - |n| PyFilterExpr(Arc::new(n.eq(value.clone()))), - |t| PyFilterExpr(Arc::new(t.eq(value.clone()))), - ) - } - - fn __ne__(&self, value: String) -> PyFilterExpr { - 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 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 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 { - 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 { - 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 { - 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 { - self.map( - |n| PyFilterExpr(Arc::new(n.not_contains(value.clone()))), - |t| PyFilterExpr(Arc::new(t.not_contains(value.clone()))), - ) - } - - fn fuzzy_search( - &self, - value: String, - levenshtein_distance: usize, - prefix_match: bool, - ) -> PyFilterExpr { - 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 = "ExplodedEdgeEndpoint", module = "raphtory.filter")] -#[derive(Clone)] -pub struct PyExplodedEdgeEndpoint(pub ExplodedEdgeEndpoint); - -#[pymethods] -impl PyExplodedEdgeEndpoint { - fn id(&self) -> PyExplodedEdgeIdFilterOp { - PyExplodedEdgeIdFilterOp(self.0.id()) - } - - fn name(&self) -> PyExplodedEdgeFilterOp { - PyExplodedEdgeFilterOp::from(self.0.name()) - } - - fn node_type(&self) -> PyExplodedEdgeFilterOp { - PyExplodedEdgeFilterOp::from(self.0.node_type()) - } - - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder> = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = self.0.metadata(name); - b.into_pyobject(py) - } - - fn window(&self, py_start: PyTime, py_end: PyTime) -> PyResult { - Ok(PyExplodedEdgeEndpointWindow( - self.0.window(py_start, py_end), - )) - } -} #[pyclass(frozen, name = "ExplodedEdge", module = "raphtory.filter")] #[derive(Clone)] @@ -255,16 +20,6 @@ pub struct PyExplodedEdgeFilter; #[pymethods] impl PyExplodedEdgeFilter { - #[staticmethod] - fn src() -> PyExplodedEdgeEndpoint { - PyExplodedEdgeEndpoint(ExplodedEdgeEndpoint::src()) - } - - #[staticmethod] - fn dst() -> PyExplodedEdgeEndpoint { - PyExplodedEdgeEndpoint(ExplodedEdgeEndpoint::dst()) - } - #[staticmethod] fn property<'py>( py: Python<'py>, @@ -291,30 +46,3 @@ impl PyExplodedEdgeFilter { ))) } } - -#[pyclass( - frozen, - name = "ExplodedEdgeEndpointWindow", - module = "raphtory.filter" -)] -#[derive(Clone)] -pub struct PyExplodedEdgeEndpointWindow(pub ExplodedEndpointWrapper>); - -#[pymethods] -impl PyExplodedEdgeEndpointWindow { - fn property<'py>( - &self, - py: Python<'py>, - name: String, - ) -> PyResult> { - let b: PropertyFilterBuilder>> = - self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder>> = - self.0.metadata(name); - b.into_pyobject(py) - } -} diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 0d2afe2053..87e39fca6b 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -1,9 +1,6 @@ use crate::python::filter::{ edge_filter_builders::{PyEdgeEndpoint, PyEdgeFilter, PyEdgeFilterOp, PyEdgeIdFilterOp}, - exploded_edge_filter_builder::{ - PyExplodedEdgeEndpoint, PyExplodedEdgeFilter, PyExplodedEdgeFilterOp, - PyExplodedEdgeIdFilterOp, - }, + exploded_edge_filter_builder::PyExplodedEdgeFilter, filter_expr::PyFilterExpr, node_filter_builders::PyNodeFilter, property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, @@ -39,9 +36,6 @@ pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { 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 1bf9b932bc..d0f1b67bad 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -2,76 +2,25 @@ use crate::{ db::graph::views::filter::model::{ node_filter::{ InternalNodeFilterBuilderOps, NodeFilter, NodeFilterBuilderOps, NodeIdFilterBuilder, - NodeIdFilterBuilderOps, + 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}, + window_filter::PyNodeWindow, + }, 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); @@ -141,115 +90,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, NodeFilter, - ))) +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( @@ -258,11 +211,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) -> PyResult { + Ok(PyNodeWindow(Windowed::from_times(start, end, NodeFilter))) } } diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index 3e5e5b134b..13e3101877 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -3,14 +3,17 @@ use crate::{ internal::CreateFilter, model::{ property_filter::{ - ElemQualifierOps, ListAggOps, MetadataFilterBuilder, PropertyFilterBuilder, - PropertyFilterOps, + ElemQualifierOps, InternalPropertyFilterBuilderOps, ListAggOps, + MetadataFilterBuilder, OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, }, TryAsCompositeFilter, }, }, prelude::PropertyFilter, - python::{filter::filter_expr::PyFilterExpr, types::iterable::FromIterable}, + python::{ + filter::{create_filter::DynInternalFilterOps, filter_expr::PyFilterExpr}, + types::iterable::FromIterable, + }, }; use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyErr, Python}; use raphtory_api::core::entities::properties::prop::Prop; @@ -74,7 +77,8 @@ pub trait DynFilterOps: Send + Sync { impl DynFilterOps for T where T: PropertyFilterOps + ElemQualifierOps + ListAggOps + Clone + Send + Sync + 'static, - PropertyFilter: CreateFilter + TryAsCompositeFilter, + T::Wrapped>: CreateFilter + TryAsCompositeFilter + Clone, + T::Wrapped>: InternalPropertyFilterBuilderOps + Clone, { fn __eq__(&self, value: Prop) -> PyFilterExpr { PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) @@ -97,7 +101,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 { @@ -147,7 +152,8 @@ where } fn any(&self) -> PyFilterOps { - PyFilterOps::wrap(ElemQualifierOps::any(self)) + let filter = ElemQualifierOps::any(self); + PyFilterOps::wrap(filter) } fn all(&self) -> PyFilterOps { @@ -190,7 +196,7 @@ pub struct PyFilterOps { } impl PyFilterOps { - fn wrap(t: T) -> Self { + pub fn wrap(t: T) -> Self { Self { ops: Arc::new(t) } } diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs index ed5edccaf4..dec399f45b 100644 --- a/raphtory/src/python/filter/window_filter.rs +++ b/raphtory/src/python/filter/window_filter.rs @@ -1,14 +1,19 @@ use crate::{ - db::graph::views::filter::model::{ - edge_filter::EdgeFilter, - exploded_edge_filter::ExplodedEdgeFilter, - node_filter::NodeFilter, - property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - Windowed, + db::graph::views::filter::{ + internal::CreateFilter, + model::{ + edge_filter::EdgeFilter, + exploded_edge_filter::ExplodedEdgeFilter, + node_filter::NodeFilter, + property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, + PropertyFilterFactory, TryAsCompositeFilter, Windowed, + }, }, + prelude::PropertyFilter, python::filter::property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, }; use pyo3::prelude::*; +use std::sync::Arc; #[pyclass(frozen, name = "NodeWindow", module = "raphtory.filter")] #[derive(Clone)] @@ -21,14 +26,12 @@ impl PyNodeWindow { py: Python<'py>, name: String, ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); + let b = self.0.property(name); b.into_pyobject(py) } fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); + let b = self.0.metadata(name); b.into_pyobject(py) } } @@ -44,14 +47,12 @@ impl PyEdgeWindow { py: Python<'py>, name: String, ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); + let b = self.0.property(name); b.into_pyobject(py) } fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); + let b = self.0.metadata(name); b.into_pyobject(py) } } @@ -67,14 +68,12 @@ impl PyExplodedEdgeWindow { py: Python<'py>, name: String, ) -> PyResult> { - let b: PropertyFilterBuilder> = - PropertyFilterBuilder::new(name, self.0.clone()); + let b = self.0.property(name); b.into_pyobject(py) } fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b: MetadataFilterBuilder> = - MetadataFilterBuilder::new(name, self.0.clone()); + let b = self.0.metadata(name); b.into_pyobject(py) } } diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index 7e5c8a938d..25ae2dfa8a 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -3,7 +3,7 @@ use crate::{ db::{ api::{ properties::{Metadata, Properties}, - state::ops::filter::NodePropertyFilterOp, + state::ops::filter::NodeTypeFilterOp, view::{ filter_ops::Filter, internal::{ @@ -23,7 +23,7 @@ use crate::{ filter::{ edge_property_filtered_graph::EdgePropertyFilteredGraph, exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, - node_type_filtered_graph::NodeTypeFilteredGraph, + node_filtered_graph::NodeFilteredGraph, }, layer_graph::LayeredGraph, node_subgraph::NodeSubgraph, @@ -137,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; @@ -157,16 +147,6 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for EdgePropert } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropertyFilterOp { - 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 { @@ -443,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/types/macros/trait_impl/filter_ops.rs b/raphtory/src/python/types/macros/trait_impl/filter_ops.rs index c071a64029..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,7 +23,7 @@ macro_rules! impl_filter_ops { fn filter( &self, filter: PyFilterExpr, - ) -> Result<<$base_type as Filter<'static>>::Filtered, GraphError> { + ) -> PyResult<<$base_type as InternalFilter<'static>>::Filtered> { Ok(self.$field.clone().filter(filter)?.into_dyn_hop()) } } From 2c976c8d655f4390d1cdcfdeef04d7585609661a Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Tue, 25 Nov 2025 11:55:13 +0100 Subject: [PATCH 41/42] Do not rely on Wrap for the trait bounds in the builder API as the compiler cannot figure out the bounds when trying to implement python wrappers. --- .../graph/views/filter/model/edge_filter.rs | 13 +- .../filter/model/exploded_edge_filter.rs | 15 +- .../src/db/graph/views/filter/model/mod.rs | 150 +++++++++++--- .../views/filter/model/property_filter.rs | 196 +++++++++++------- .../src/python/filter/edge_filter_builders.rs | 9 +- .../filter/exploded_edge_filter_builder.rs | 13 +- raphtory/src/python/filter/mod.rs | 4 - .../src/python/filter/node_filter_builders.rs | 9 +- .../python/filter/property_filter_builders.rs | 72 +++++-- raphtory/src/python/filter/window_filter.rs | 63 ------ 10 files changed, 342 insertions(+), 202 deletions(-) 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 a416796bba..6ec0a31b8e 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -15,7 +15,8 @@ use crate::{ NodeNameFilterBuilder, NodeTypeFilterBuilder, }, property_filter::{ - InternalPropertyFilterBuilderOps, Op, PropertyFilter, PropertyRef, + InternalPropertyFilterBuilderOps, Op, OpChainBuilder, PropertyFilter, + PropertyRef, }, AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, @@ -204,6 +205,8 @@ impl EndpointWrapper { } impl InternalPropertyFilterBuilderOps for EndpointWrapper { + type Filter = EndpointWrapper; + type Chained = EndpointWrapper; type Marker = T::Marker; #[inline] @@ -220,6 +223,14 @@ impl InternalPropertyFilterBuilderOps for E 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)) + } } impl InternalNodeFilterBuilderOps for EndpointWrapper { 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 index 022c7d32ae..d5b3c49a90 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -15,8 +15,8 @@ use crate::{ InternalNodeIdFilterBuilderOps, NodeFilter, }, property_filter::{ - InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, PropertyFilter, - PropertyFilterBuilder, PropertyRef, + InternalPropertyFilterBuilderOps, MetadataFilterBuilder, Op, OpChainBuilder, + PropertyFilter, PropertyFilterBuilder, PropertyRef, }, AndFilter, EntityMarker, NotFilter, OrFilter, TryAsCompositeFilter, Windowed, Wrap, }, @@ -253,6 +253,9 @@ where impl InternalPropertyFilterBuilderOps for ExplodedEndpointWrapper { + type Filter = ExplodedEndpointWrapper; + type Chained = ExplodedEndpointWrapper; + type Marker = T::Marker; #[inline] fn property_ref(&self) -> PropertyRef { @@ -268,6 +271,14 @@ impl InternalPropertyFilterBuilderOps 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)] diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs index 215c4de872..4cc0dcaf29 100644 --- a/raphtory/src/db/graph/views/filter/model/mod.rs +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -320,6 +320,8 @@ impl InternalNodeIdFilterBuilderOps for Windo } impl InternalPropertyFilterBuilderOps for Windowed { + type Filter = Windowed; + type Chained = Windowed; type Marker = T::Marker; fn property_ref(&self) -> PropertyRef { @@ -333,6 +335,14 @@ impl InternalPropertyFilterBuilderOps for W 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)) + } } impl TryAsCompositeFilter for Windowed { @@ -478,105 +488,197 @@ impl Wrap for Arc { } } -pub trait InternalPropertyFilterFactory: Wrap { +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::Wrapped> { + fn property(&self, name: impl Into) -> Self::PropertyBuilder { let builder = PropertyFilterBuilder::new(name, self.entity()); - self.wrap(builder) + self.property_builder(builder) } - fn metadata( - &self, - name: impl Into, - ) -> Self::Wrapped> { + fn metadata(&self, name: impl Into) -> Self::MetadataBuilder { let builder = MetadataFilterBuilder::new(name, self.entity()); - self.wrap(builder) + self.metadata_builder(builder) } } -impl PropertyFilterFactory for ExplodedEndpointWrapper {} +impl PropertyFilterFactory for T {} pub trait TemporalPropertyFilterFactory: InternalPropertyFilterBuilderOps { - fn temporal(&self) -> Self::Wrapped> { + fn temporal(&self) -> Self::Chained { let builder = OpChainBuilder { prop_ref: PropertyRef::TemporalProperty(self.property_ref().name().to_string()), ops: vec![], entity: self.entity(), }; - self.wrap(builder) + self.chained(builder) } } impl InternalPropertyFilterFactory for NodeFilter { type Entity = NodeFilter; + type PropertyBuilder = PropertyFilterBuilder; + type MetadataBuilder = MetadataFilterBuilder; fn entity(&self) -> Self::Entity { NodeFilter } -} -impl PropertyFilterFactory for 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 } -} -impl PropertyFilterFactory for 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 } -} -impl PropertyFilterFactory for 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 PropertyFilterFactory 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 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 EndpointWrapper {} -impl PropertyFilterFactory 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 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 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 c6f05d90ed..dd0975831c 100644 --- a/raphtory/src/db/graph/views/filter/model/property_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -1350,17 +1350,27 @@ pub trait CombinedFilter: CreateFilter + TryAsCompositeFilter + Clone + 'static impl CombinedFilter for T {} -pub trait InternalPropertyFilterBuilderOps: Send + Sync + Wrap { - type Marker: Clone + Send + Sync + 'static; +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 entity(&self) -> Self::Marker; + + fn filter(&self, filter: PropertyFilter) -> Self::Filter; + + fn chained(&self, builder: OpChainBuilder) -> Self::Chained; } impl InternalPropertyFilterBuilderOps for Arc { + type Filter = T::Filter; + type Chained = T::Chained; type Marker = T::Marker; fn property_ref(&self) -> PropertyRef { @@ -1374,39 +1384,41 @@ impl InternalPropertyFilterBuilderOps for A 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: InternalPropertyFilterBuilderOps { - fn eq(&self, value: impl Into) -> Self::Wrapped>; - fn ne(&self, value: impl Into) -> Self::Wrapped>; - fn le(&self, value: impl Into) -> Self::Wrapped>; - fn ge(&self, value: impl Into) -> Self::Wrapped>; - fn lt(&self, value: impl Into) -> Self::Wrapped>; - fn gt(&self, value: impl Into) -> Self::Wrapped>; - fn is_in( - &self, - values: impl IntoIterator, - ) -> Self::Wrapped>; - fn is_not_in( - &self, - values: impl IntoIterator, - ) -> Self::Wrapped>; - fn is_none(&self) -> Self::Wrapped>; - fn is_some(&self) -> Self::Wrapped>; - fn starts_with(&self, value: impl Into) -> Self::Wrapped>; - fn ends_with(&self, value: impl Into) -> Self::Wrapped>; - fn contains(&self, value: impl Into) -> Self::Wrapped>; - fn not_contains(&self, value: impl Into) -> Self::Wrapped>; + 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, - ) -> Self::Wrapped>; + ) -> Self::Filter; } impl PropertyFilterOps for T { - fn eq(&self, value: impl Into) -> Self::Wrapped> { + fn eq(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1414,10 +1426,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn ne(&self, value: impl Into) -> Self::Wrapped> { + fn ne(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1425,10 +1437,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn le(&self, value: impl Into) -> Self::Wrapped> { + fn le(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1436,10 +1448,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn ge(&self, value: impl Into) -> Self::Wrapped> { + fn ge(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1447,10 +1459,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn lt(&self, value: impl Into) -> Self::Wrapped> { + fn lt(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1458,10 +1470,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn gt(&self, value: impl Into) -> Self::Wrapped> { + fn gt(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1469,13 +1481,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn is_in( - &self, - values: impl IntoIterator, - ) -> Self::Wrapped> { + 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())), @@ -1483,13 +1492,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn is_not_in( - &self, - values: impl IntoIterator, - ) -> Self::Wrapped> { + 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())), @@ -1497,10 +1503,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn is_none(&self) -> Self::Wrapped> { + fn is_none(&self) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, @@ -1508,10 +1514,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn is_some(&self) -> Self::Wrapped> { + fn is_some(&self) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::None, @@ -1519,10 +1525,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn starts_with(&self, value: impl Into) -> Self::Wrapped> { + fn starts_with(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1530,10 +1536,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn ends_with(&self, value: impl Into) -> Self::Wrapped> { + fn ends_with(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1541,10 +1547,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn contains(&self, value: impl Into) -> Self::Wrapped> { + fn contains(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1552,10 +1558,10 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } - fn not_contains(&self, value: impl Into) -> Self::Wrapped> { + fn not_contains(&self, value: impl Into) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(value.into()), @@ -1563,7 +1569,7 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } fn fuzzy_search( @@ -1571,7 +1577,7 @@ impl PropertyFilterOps for T { prop_value: impl Into, levenshtein_distance: usize, prefix_match: bool, - ) -> Self::Wrapped> { + ) -> Self::Filter { let filter = PropertyFilter { prop_ref: self.property_ref(), prop_value: PropertyFilterValue::Single(Prop::Str(ArcStr::from(prop_value.into()))), @@ -1582,7 +1588,7 @@ impl PropertyFilterOps for T { ops: self.ops().to_vec(), entity: self.entity(), }; - self.wrap(filter) + self.filter(filter) } } @@ -1607,7 +1613,10 @@ 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 { @@ -1621,6 +1630,14 @@ where 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)] @@ -1644,7 +1661,10 @@ 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 { @@ -1658,6 +1678,14 @@ where 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)] @@ -1728,6 +1756,8 @@ where M: Send + Sync + Clone + 'static, PropertyFilter: CombinedFilter, { + type Filter = PropertyFilter; + type Chained = OpChainBuilder; type Marker = M; fn property_ref(&self) -> PropertyRef { @@ -1741,10 +1771,18 @@ where 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: InternalPropertyFilterBuilderOps { - fn any(&self) -> Self::Wrapped> + fn any(&self) -> Self::Chained where Self: Sized, { @@ -1753,10 +1791,10 @@ pub trait ElemQualifierOps: InternalPropertyFilterBuilderOps { ops: self.ops().iter().copied().chain([Op::Any]).collect(), entity: self.entity(), }; - self.wrap(builder) + self.chained(builder) } - fn all(&self) -> Self::Wrapped> + fn all(&self) -> Self::Chained where Self: Sized, { @@ -1765,7 +1803,7 @@ pub trait ElemQualifierOps: InternalPropertyFilterBuilderOps { ops: self.ops().iter().copied().chain([Op::All]).collect(), entity: self.entity(), }; - self.wrap(builder) + self.chained(builder) } } @@ -1781,69 +1819,69 @@ impl PropertyFilterBuilder { } } -pub trait ListAggOps: InternalPropertyFilterBuilderOps + Sized { - fn len(&self) -> Self::Wrapped> { +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.wrap(builder) + self.chained(builder) } - fn sum(&self) -> Self::Wrapped> { + 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.wrap(builder) + self.chained(builder) } - fn avg(&self) -> Self::Wrapped> { + 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.wrap(builder) + self.chained(builder) } - fn min(&self) -> Self::Wrapped> { + 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.wrap(builder) + self.chained(builder) } - fn max(&self) -> Self::Wrapped> { + 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.wrap(builder) + self.chained(builder) } - fn first(&self) -> Self::Wrapped> { + 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.wrap(builder) + self.chained(builder) } - fn last(&self) -> Self::Wrapped> { + 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(), }; - self.wrap(builder) + self.chained(builder) } } -impl ListAggOps for T {} +impl ListAggOps for T {} diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index a4a1a9d815..df65d066c4 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -12,8 +12,9 @@ use crate::{ python::{ filter::{ filter_expr::PyFilterExpr, - property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, - window_filter::PyEdgeWindow, + property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, + }, }, types::iterable::FromIterable, utils::PyTime, @@ -265,7 +266,7 @@ impl PyEdgeFilter { } #[staticmethod] - fn window(start: PyTime, end: PyTime) -> PyResult { - Ok(PyEdgeWindow(Windowed::from_times(start, end, EdgeFilter))) + 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 index baf2d94ce2..1a6db02b11 100644 --- a/raphtory/src/python/filter/exploded_edge_filter_builder.rs +++ b/raphtory/src/python/filter/exploded_edge_filter_builder.rs @@ -5,9 +5,8 @@ use crate::{ PropertyFilterFactory, Windowed, }, python::{ - filter::{ - property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, - window_filter::PyExplodedEdgeWindow, + filter::property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, }, utils::PyTime, }, @@ -38,11 +37,7 @@ impl PyExplodedEdgeFilter { } #[staticmethod] - fn window(start: PyTime, end: PyTime) -> PyResult { - Ok(PyExplodedEdgeWindow(Windowed::from_times( - start, - end, - ExplodedEdgeFilter, - ))) + fn window(start: PyTime, end: PyTime) -> PyPropertyFilterFactory { + PyPropertyFilterFactory::wrap(ExplodedEdgeFilter::window(start, end)) } } diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index 87e39fca6b..cefe53a344 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -4,7 +4,6 @@ use crate::python::filter::{ filter_expr::PyFilterExpr, node_filter_builders::PyNodeFilter, property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, - window_filter::{PyEdgeWindow, PyExplodedEdgeWindow, PyNodeWindow}, }; use pyo3::{ prelude::{PyModule, PyModuleMethods}, @@ -27,16 +26,13 @@ pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { 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 d0f1b67bad..cdacb16d77 100644 --- a/raphtory/src/python/filter/node_filter_builders.rs +++ b/raphtory/src/python/filter/node_filter_builders.rs @@ -10,8 +10,9 @@ use crate::{ python::{ filter::{ filter_expr::PyFilterExpr, - property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, - window_filter::PyNodeWindow, + property_filter_builders::{ + PyFilterOps, PyPropertyFilterBuilder, PyPropertyFilterFactory, + }, }, types::iterable::FromIterable, utils::PyTime, @@ -271,7 +272,7 @@ impl PyNodeFilter { } #[staticmethod] - fn window(start: PyTime, end: PyTime) -> PyResult { - Ok(PyNodeWindow(Windowed::from_times(start, end, NodeFilter))) + 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 13e3101877..29c32bcb36 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -6,7 +6,7 @@ use crate::{ ElemQualifierOps, InternalPropertyFilterBuilderOps, ListAggOps, MetadataFilterBuilder, OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, }, - TryAsCompositeFilter, + PropertyFilterFactory, TemporalPropertyFilterFactory, TryAsCompositeFilter, }, }, prelude::PropertyFilter, @@ -19,7 +19,7 @@ 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; @@ -54,7 +54,9 @@ pub trait DynFilterOps: Send + Sync { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr; +} +pub trait DynListFilterOps: Send + Sync { fn any(&self) -> PyFilterOps; fn all(&self) -> PyFilterOps; @@ -74,12 +76,7 @@ pub trait DynFilterOps: Send + Sync { fn last(&self) -> PyFilterOps; } -impl DynFilterOps for T -where - T: PropertyFilterOps + ElemQualifierOps + ListAggOps + Clone + Send + Sync + 'static, - T::Wrapped>: CreateFilter + TryAsCompositeFilter + Clone, - T::Wrapped>: InternalPropertyFilterBuilderOps + Clone, -{ +impl DynPropertyFilterOps for T { fn __eq__(&self, value: Prop) -> PyFilterExpr { PyFilterExpr(Arc::new(PropertyFilterOps::eq(self, value))) } @@ -150,7 +147,12 @@ where prefix_match, ))) } +} +impl DynListFilterOps for T +where + T: InternalPropertyFilterBuilderOps + 'static, +{ fn any(&self) -> PyFilterOps { let filter = ElemQualifierOps::any(self); PyFilterOps::wrap(filter) @@ -189,6 +191,10 @@ where } } +pub trait DynFilterOps: DynListFilterOps + DynPropertyFilterOps {} + +impl DynFilterOps for T {} + #[pyclass(frozen, name = "FilterOps", module = "raphtory.filter", subclass)] #[derive(Clone)] pub struct PyFilterOps { @@ -331,13 +337,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()) } } @@ -351,6 +356,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>; @@ -367,6 +373,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>; @@ -413,3 +420,44 @@ impl<'py> IntoPyObject<'py> for PyPropertyFilterBuilder { // PyPropertyFilterBuilder::from_arc(Arc::new(self)).into_pyobject(py) // } // } + +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 index dec399f45b..1507d31726 100644 --- a/raphtory/src/python/filter/window_filter.rs +++ b/raphtory/src/python/filter/window_filter.rs @@ -14,66 +14,3 @@ use crate::{ }; use pyo3::prelude::*; use std::sync::Arc; - -#[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 = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b = self.0.metadata(name); - 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 = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b = self.0.metadata(name); - 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 = self.0.property(name); - b.into_pyobject(py) - } - - fn metadata<'py>(&self, py: Python<'py>, name: String) -> PyResult> { - let b = self.0.metadata(name); - b.into_pyobject(py) - } -} From 1a6fc7459cf0166838e1a8630f735a490e7ef14a Mon Sep 17 00:00:00 2001 From: shivamka1 <4599890+shivamka1@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:34:26 +0000 Subject: [PATCH 42/42] fix gql, tests --- python/python/raphtory/filter/__init__.pyi | 114 +----------------- raphtory-benchmark/benches/search_bench.rs | 27 +++-- raphtory-graphql/graphs/g/.raph | 1 - raphtory-graphql/graphs/g/graph | Bin 1678 -> 0 bytes raphtory-graphql/graphs/graph/.raph | 1 - raphtory-graphql/graphs/graph/graph | Bin 32 -> 0 bytes .../graphs/path/to/event_graph/.raph | 1 - .../graphs/path/to/event_graph/graph | Bin 2583 -> 0 bytes .../graphs/test/first/internal/graph/.raph | 1 - .../graphs/test/first/internal/graph/graph | Bin 32 -> 0 bytes raphtory-graphql/graphs/test/graph/.raph | 1 - raphtory-graphql/graphs/test/graph/graph | Bin 32 -> 0 bytes .../graphs/test/second/internal/graph1/.raph | 1 - .../graphs/test/second/internal/graph1/graph | Bin 32 -> 0 bytes .../graphs/test/second/internal/graph2/.raph | 1 - .../graphs/test/second/internal/graph2/graph | Bin 32 -> 0 bytes raphtory-graphql/schema.graphql | 2 +- raphtory-graphql/src/model/graph/filtering.rs | 54 ++------- .../src/model/schema/node_schema.rs | 13 +- raphtory/src/db/graph/views/filter/mod.rs | 84 ++++++++----- .../graph/views/filter/model/edge_filter.rs | 18 +-- .../filter/model/exploded_edge_filter.rs | 18 +-- .../graph/views/filter/model/node_filter.rs | 3 +- .../graph/views/filter/node_filtered_graph.rs | 2 +- .../src/python/filter/edge_filter_builders.rs | 14 +++ raphtory/src/python/filter/mod.rs | 1 - .../python/filter/property_filter_builders.rs | 64 +++++----- raphtory/src/python/filter/window_filter.rs | 16 --- 28 files changed, 156 insertions(+), 281 deletions(-) delete mode 100644 raphtory-graphql/graphs/g/.raph delete mode 100644 raphtory-graphql/graphs/g/graph delete mode 100644 raphtory-graphql/graphs/graph/.raph delete mode 100644 raphtory-graphql/graphs/graph/graph delete mode 100644 raphtory-graphql/graphs/path/to/event_graph/.raph delete mode 100644 raphtory-graphql/graphs/path/to/event_graph/graph delete mode 100644 raphtory-graphql/graphs/test/first/internal/graph/.raph delete mode 100644 raphtory-graphql/graphs/test/first/internal/graph/graph delete mode 100644 raphtory-graphql/graphs/test/graph/.raph delete mode 100644 raphtory-graphql/graphs/test/graph/graph delete mode 100644 raphtory-graphql/graphs/test/second/internal/graph1/.raph delete mode 100644 raphtory-graphql/graphs/test/second/internal/graph1/graph delete mode 100644 raphtory-graphql/graphs/test/second/internal/graph2/.raph delete mode 100644 raphtory-graphql/graphs/test/second/internal/graph2/graph delete mode 100644 raphtory/src/python/filter/window_filter.rs diff --git a/python/python/raphtory/filter/__init__.pyi b/python/python/raphtory/filter/__init__.pyi index 8d4fb95ec8..9d06c8401c 100644 --- a/python/python/raphtory/filter/__init__.pyi +++ b/python/python/raphtory/filter/__init__.pyi @@ -27,17 +27,11 @@ __all__ = [ "FilterOps", "PropertyFilterOps", "Node", - "NodeWindow", "Edge", "EdgeEndpoint", "EdgeFilterOp", "EdgeIdFilterOp", - "EdgeWindow", "ExplodedEdge", - "ExplodedEdgeEndpoint", - "ExplodedEdgeFilterOp", - "ExplodedEdgeIdFilterOp", - "ExplodedEdgeWindow", ] class FilterExpr(object): @@ -99,42 +93,17 @@ class PropertyFilterOps(FilterOps): class Node(object): @staticmethod - def id(): - """ - Filter node by id - - Returns: - NodeFilterBuilder: A filter builder for filtering by node id - """ - + def id(): ... @staticmethod def metadata(name): ... @staticmethod - def name(): - """ - Filter node by name - - Returns: - NodeFilterBuilder: A filter builder for filtering by node name - """ - + def name(): ... @staticmethod - def node_type(): - """ - Filter node by type - - Returns: - NodeFilterBuilder: A filter builder for filtering by node type - """ - + def node_type(): ... @staticmethod def property(name): ... @staticmethod - def window(py_start, py_end): ... - -class NodeWindow(object): - def metadata(self, name): ... - def property(self, name): ... + def window(start, end): ... class Edge(object): @staticmethod @@ -154,7 +123,6 @@ class EdgeEndpoint(object): def name(self): ... def node_type(self): ... def property(self, name): ... - def window(self, py_start, py_end): ... class EdgeFilterOp(object): def __eq__(self, value): @@ -210,84 +178,10 @@ class EdgeIdFilterOp(object): def not_contains(self, value): ... def starts_with(self, value): ... -class EdgeWindow(object): - def metadata(self, name): ... - def property(self, name): ... - class ExplodedEdge(object): - @staticmethod - def dst(): ... @staticmethod def metadata(name): ... @staticmethod def property(name): ... @staticmethod - def src(): ... - @staticmethod def window(start, end): ... - -class ExplodedEdgeEndpoint(object): - def id(self): ... - def metadata(self, name): ... - def name(self): ... - def node_type(self): ... - def property(self, name): ... - def window(self, py_start, py_end): ... - -class ExplodedEdgeFilterOp(object): - def __eq__(self, value): - """Return self==value.""" - - def __ge__(self, value): - """Return self>=value.""" - - def __gt__(self, value): - """Return self>value.""" - - def __le__(self, value): - """Return self<=value.""" - - def __lt__(self, value): - """Return self=value.""" - - def __gt__(self, value): - """Return self>value.""" - - def __le__(self, value): - """Return self<=value.""" - - def __lt__(self, value): - """Return self( sampled_values: Option>, ) -> Option> where - M: PropertyFilterFactory + Default, - PropertyFilterBuilder: PropertyFilterOps + InternalPropertyFilterBuilderOps, + 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/graphs/g/.raph b/raphtory-graphql/graphs/g/.raph deleted file mode 100644 index 975d616f5a..0000000000 --- a/raphtory-graphql/graphs/g/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":15,"edge_count":15,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/g/graph b/raphtory-graphql/graphs/g/graph deleted file mode 100644 index eabae6fc31366693c18816def9266006b58d46f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1678 zcmY*Y$#T>%5RGM9mSx!yX6rbt!!~hPGLwX5J|Z8$g`x^LaR3T_AYabwmg4HUOsn-4 zwWT*>`0Ty=^6mSNum67j9mBZSP2QVdn^onFtHvLpcD-%~C116ipbsge4L+|>B;YI8 zT@2KrhS*a;?c+fGs&WGjvzZQNKe}k=YBXyzYQ3w7I)?q z%R<#DmK9b5R#2=4H{45)@59WEupHiD$h$nV%XbmS0e5WZaK>BVb`FHiJ5ksr(&${P zZq5U{l`&RxKQ3x!d*$5$y}h9PtsUmQqr5xC4q)HiV+XMBDRxlo9_;%In@{dD>PG7Z zpd`}|c{l7l?)gaEkPdVWP0|&!-SJBuN#$L)wNp;ZE~Vu-GY9D#dXRnGj#F;wcup%p zwvr$)IYAGzlJVRFM_zz)mL4fQ^?r*~$@z)Jsd}u==UJbq^Apbg8G%2N27gZ4Q>Emj zt<-r5XJtm9MEaoQ^gUBbPTzB-OgJku0wod&B`5NQQgR|+N^j(j63)tuK#7z>$tiuM zn5pO6aQQXjF6DK-ygTbSuP34_utP$4hE6$f*5B?qaMs@`hs0oqgs?+U4xIn9#esQghk1SG@h<-p KHu42S+WimOy@DJ7 diff --git a/raphtory-graphql/graphs/graph/.raph b/raphtory-graphql/graphs/graph/.raph deleted file mode 100644 index eb24503c48..0000000000 --- a/raphtory-graphql/graphs/graph/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/graph/graph b/raphtory-graphql/graphs/graph/graph deleted file mode 100644 index 4190f578f2e906b70834ccc29bef06c011347286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d diff --git a/raphtory-graphql/graphs/path/to/event_graph/.raph b/raphtory-graphql/graphs/path/to/event_graph/.raph deleted file mode 100644 index a5ce244763..0000000000 --- a/raphtory-graphql/graphs/path/to/event_graph/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":3,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/path/to/event_graph/graph b/raphtory-graphql/graphs/path/to/event_graph/graph deleted file mode 100644 index 51853dfccbdcc0e8b4468a19e0637641a253523e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2583 zcmc&#O>fgc5RKO#*-R)b)1);njiQP}wNg>yHtEMHDkp9n;L=Ov(6o(|FQq9GoT%XE z@gtD9@FP%RcAcb7v7M#|u(Yx@wr6(Ud-E0_Apmclw$FmA?pQJ}gB$W0tPV$mVY44} zKD3*s!MHu{^x9htGFX(Wkk>CS$D>aFTo^jMCJO*M9E?W6N5Qr%U>tB=7GXulr&)xl ze<(-SZ>YW{geNZ@3$(U5y^fqF9r-oGbYuVLY6o~@5BYRZO~%0Oj<{rD}d69XBzS=!)qp( zYfP}nOt4v(J;LDM?Ox`I!7 zM7c;{sba0~&TWnGjcWI<>h3GEbPAW1TY_lQRJerP;NLJ`s{U#C5@sn8jU{xtU!Tp=0$19fx{`9EjI@rGEB4{BH zT}!IFayrf6h7kkt$>yL_u0@%T@U RvQVAr`X=C{b8K;9;}3HPFsc9m diff --git a/raphtory-graphql/graphs/test/first/internal/graph/.raph b/raphtory-graphql/graphs/test/first/internal/graph/.raph deleted file mode 100644 index eb24503c48..0000000000 --- a/raphtory-graphql/graphs/test/first/internal/graph/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/first/internal/graph/graph b/raphtory-graphql/graphs/test/first/internal/graph/graph deleted file mode 100644 index 4190f578f2e906b70834ccc29bef06c011347286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d diff --git a/raphtory-graphql/graphs/test/graph/.raph b/raphtory-graphql/graphs/test/graph/.raph deleted file mode 100644 index eb24503c48..0000000000 --- a/raphtory-graphql/graphs/test/graph/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/graph/graph b/raphtory-graphql/graphs/test/graph/graph deleted file mode 100644 index 4190f578f2e906b70834ccc29bef06c011347286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d diff --git a/raphtory-graphql/graphs/test/second/internal/graph1/.raph b/raphtory-graphql/graphs/test/second/internal/graph1/.raph deleted file mode 100644 index eb24503c48..0000000000 --- a/raphtory-graphql/graphs/test/second/internal/graph1/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/second/internal/graph1/graph b/raphtory-graphql/graphs/test/second/internal/graph1/graph deleted file mode 100644 index 4190f578f2e906b70834ccc29bef06c011347286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d diff --git a/raphtory-graphql/graphs/test/second/internal/graph2/.raph b/raphtory-graphql/graphs/test/second/internal/graph2/.raph deleted file mode 100644 index eb24503c48..0000000000 --- a/raphtory-graphql/graphs/test/second/internal/graph2/.raph +++ /dev/null @@ -1 +0,0 @@ -{"node_count":1,"edge_count":0,"metadata":[]} \ No newline at end of file diff --git a/raphtory-graphql/graphs/test/second/internal/graph2/graph b/raphtory-graphql/graphs/test/second/internal/graph2/graph deleted file mode 100644 index 4190f578f2e906b70834ccc29bef06c011347286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 jcmWgQ5#r+Fh)+pPODxSPkzx{H)MAoi&|(n+v$z-lTsZ`d diff --git a/raphtory-graphql/schema.graphql b/raphtory-graphql/schema.graphql index 7abc1bb19f..09d68b44b3 100644 --- a/raphtory-graphql/schema.graphql +++ b/raphtory-graphql/schema.graphql @@ -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/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 1d4ad8c964..0c4c8c64f9 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -868,20 +868,6 @@ fn build_property_filter_from_condition_with_entity( - prop_ref: PropertyRef, - cond: &PropCondition, - start: i64, - end: i64, - marker: M, -) -> Result>, GraphError> { - build_property_filter_from_condition_with_entity::>( - prop_ref, - cond, - Windowed::from_times(start, end, marker), - ) -} - fn build_node_filter_from_prop_condition( prop_ref: PropertyRef, cond: &PropCondition, @@ -950,22 +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, - NodeFilter, - )?; - 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); @@ -1059,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, EdgeFilter, - )?; - 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/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/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs index c3198c4bd7..2b8d8f6cfb 100644 --- a/raphtory/src/db/graph/views/filter/mod.rs +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -5,7 +5,7 @@ pub mod exploded_edge_node_filtered_graph; pub mod exploded_edge_property_filter; pub(crate) mod internal; pub mod model; -pub(crate) mod node_filtered_graph; +pub mod node_filtered_graph; pub mod not_filtered_graph; pub mod or_filtered_graph; @@ -1537,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, NodeIdFilterBuilderOps}, - 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"); @@ -10219,29 +10237,29 @@ pub(crate) mod test_filters { }, }; - // #[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() - // .name() - // .eq("3") - // .and(EdgeFilter::dst().name().eq("1")); - // let expected_results = vec!["3->1"]; - // assert_filter_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::EventOnly, - // ); - // assert_search_edges_results( - // init_edges_graph, - // IdentityGraphTransformer, - // filter.clone(), - // &expected_results, - // TestVariants::All, - // ); - // } + #[test] + fn test_filter_edge_for_src_dst() { + // TODO: PropertyFilteringNotImplemented for variants persistent_graph, persistent_disk_graph for filter_edges. + let filter = EdgeFilter::src() + .name() + .eq("3") + .and(EdgeFilter::dst().name().eq("1")); + let expected_results = vec!["3->1"]; + assert_filter_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::EventOnly, + ); + assert_search_edges_results( + init_edges_graph, + IdentityGraphTransformer, + filter.clone(), + &expected_results, + TestVariants::All, + ); + } #[test] fn test_unique_results_from_composite_filters() { 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 6ec0a31b8e..530ccd0f9f 100644 --- a/raphtory/src/db/graph/views/filter/model/edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -73,20 +73,14 @@ impl CreateFilter for CompositeEdgeFilter { ) -> Result, GraphError> { match self { CompositeEdgeFilter::Src(filter) => { - let filter = filter.create_node_filter(graph.clone())?; - Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::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 filter = filter.create_node_filter(graph.clone())?; - Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::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::Windowed(i) => { 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 index d5b3c49a90..018d434540 100644 --- a/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/exploded_edge_filter.rs @@ -74,20 +74,14 @@ impl CreateFilter for CompositeExplodedEdgeFilter { ) -> Result, GraphError> { match self { Self::Src(filter) => { - let filter = filter.create_node_filter(graph.clone())?; - Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::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 filter = filter.create_node_filter(graph.clone())?; - Ok(Arc::new(EdgeNodeFilteredGraph::new( - graph, - Endpoint::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) => { 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 bc30941742..d0d480e167 100644 --- a/raphtory/src/db/graph/views/filter/model/node_filter.rs +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -61,8 +61,9 @@ impl CreateFilter for NodeIdFilter { fn create_node_filter<'graph, G: GraphView + 'graph>( self, - _graph: G, + graph: G, ) -> Result, GraphError> { + NodeFilter::validate(graph.id_type(), &self.0)?; Ok(NodeIdFilterOp::new(self.0)) } } diff --git a/raphtory/src/db/graph/views/filter/node_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs index 58e195c5f8..bb941ad5a3 100644 --- a/raphtory/src/db/graph/views/filter/node_filtered_graph.rs +++ b/raphtory/src/db/graph/views/filter/node_filtered_graph.rs @@ -23,7 +23,7 @@ pub struct NodeFilteredGraph { } impl NodeFilteredGraph { - pub(crate) fn new(graph: G, filter: F) -> Self { + pub fn new(graph: G, filter: F) -> Self { Self { graph, filter } } } diff --git a/raphtory/src/python/filter/edge_filter_builders.rs b/raphtory/src/python/filter/edge_filter_builders.rs index df65d066c4..06cf52691a 100644 --- a/raphtory/src/python/filter/edge_filter_builders.rs +++ b/raphtory/src/python/filter/edge_filter_builders.rs @@ -230,6 +230,20 @@ impl PyEdgeEndpoint { 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) + } } #[pyclass(frozen, name = "Edge", module = "raphtory.filter")] diff --git a/raphtory/src/python/filter/mod.rs b/raphtory/src/python/filter/mod.rs index cefe53a344..571c589c49 100644 --- a/raphtory/src/python/filter/mod.rs +++ b/raphtory/src/python/filter/mod.rs @@ -16,7 +16,6 @@ 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")?; diff --git a/raphtory/src/python/filter/property_filter_builders.rs b/raphtory/src/python/filter/property_filter_builders.rs index 29c32bcb36..25a15ef226 100644 --- a/raphtory/src/python/filter/property_filter_builders.rs +++ b/raphtory/src/python/filter/property_filter_builders.rs @@ -2,6 +2,7 @@ use crate::{ db::graph::views::filter::{ internal::CreateFilter, model::{ + edge_filter::EndpointWrapper, property_filter::{ ElemQualifierOps, InternalPropertyFilterBuilderOps, ListAggOps, MetadataFilterBuilder, OpChainBuilder, PropertyFilterBuilder, PropertyFilterOps, @@ -10,10 +11,7 @@ use crate::{ }, }, prelude::PropertyFilter, - python::{ - filter::{create_filter::DynInternalFilterOps, filter_expr::PyFilterExpr}, - types::iterable::FromIterable, - }, + python::{filter::filter_expr::PyFilterExpr, types::iterable::FromIterable}, }; use pyo3::{pyclass, pymethods, Bound, IntoPyObject, PyErr, Python}; use raphtory_api::core::entities::properties::prop::Prop; @@ -384,18 +382,39 @@ where } } -// impl<'py, T: Clone> IntoPyObject<'py> for PropertyFilter -// where -// PropertyFilter: CreateFilter + TryAsCompositeFilter, -// { -// type Target = PyFilterExpr; -// type Output = Bound<'py, Self::Target>; -// type Error = PyErr; -// -// fn into_pyobject(self, py: Python<'py>) -> Result { -// PyFilterExpr(Arc::new(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; @@ -408,19 +427,6 @@ impl<'py> IntoPyObject<'py> for PyPropertyFilterBuilder { } } -// impl<'py, M> IntoPyObject<'py> for OpChainBuilder -// where -// PropertyFilter: CreateFilter + TryAsCompositeFilter, -// { -// type Target = PyPropertyFilterBuilder; -// type Output = Bound<'py, Self::Target>; -// type Error = PyErr; -// -// fn into_pyobject(self, py: Python<'py>) -> Result { -// PyPropertyFilterBuilder::from_arc(Arc::new(self)).into_pyobject(py) -// } -// } - pub trait DynPropertyFilterFactory: Send + Sync + 'static { fn property(&self, name: String) -> PyPropertyFilterBuilder; diff --git a/raphtory/src/python/filter/window_filter.rs b/raphtory/src/python/filter/window_filter.rs deleted file mode 100644 index 1507d31726..0000000000 --- a/raphtory/src/python/filter/window_filter.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::{ - db::graph::views::filter::{ - internal::CreateFilter, - model::{ - edge_filter::EdgeFilter, - exploded_edge_filter::ExplodedEdgeFilter, - node_filter::NodeFilter, - property_filter::{MetadataFilterBuilder, PropertyFilterBuilder}, - PropertyFilterFactory, TryAsCompositeFilter, Windowed, - }, - }, - prelude::PropertyFilter, - python::filter::property_filter_builders::{PyFilterOps, PyPropertyFilterBuilder}, -}; -use pyo3::prelude::*; -use std::sync::Arc;