diff --git a/Cargo.lock b/Cargo.lock index 4eec9490e2..155eb9d3b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4905,6 +4905,8 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "uuid", + "walkdir", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 209678830d..eb590fb89d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,7 @@ moka = { version = "0.12.7", features = ["sync"] } indexmap = { version = "2.7.0", features = ["rayon"] } fake = { version = "3.1.0", features = ["chrono"] } strsim = { version = "0.11.1" } +uuid = { version = "1.16.0", features = ["v4"] } # Make sure that transitive dependencies stick to disk_graph 50 [patch.crates-io] diff --git a/python/test_utils/utils.py b/python/test_utils/utils.py index 50b70f9d7b..34f4a3c2cf 100644 --- a/python/test_utils/utils.py +++ b/python/test_utils/utils.py @@ -3,7 +3,7 @@ import tempfile import time from typing import TypeVar, Callable - +import os import pytest from raphtory.graphql import GraphServer @@ -13,6 +13,24 @@ PORT = 1737 +if "DISK_TEST_MARK" in os.environ: + def with_disk_graph(func): + def inner(graph): + def inner2(graph, tmpdirname): + g = graph.to_disk_graph(tmpdirname) + func(g) + + func(graph) + with tempfile.TemporaryDirectory() as tmpdirname: + inner2(graph, tmpdirname) + + return inner + +else: + def with_disk_graph(func): + return func + + def measure(name: str, f: Callable[..., B], *args, print_result: bool = True) -> B: start_time = time.time() result = f(*args) diff --git a/python/tests/test_base_install/test_filters/conftest.py b/python/tests/test_base_install/test_filters/conftest.py new file mode 100644 index 0000000000..01bef3a93f --- /dev/null +++ b/python/tests/test_base_install/test_filters/conftest.py @@ -0,0 +1,54 @@ +from raphtory import Graph, PersistentGraph +from raphtory import DiskGraphStorage +import tempfile +import pytest +import shutil +import atexit + +def default_init_graph(graph): + nodes = [ + (1, 1, {"p1": "shivam_kapoor", "p9": 5, "p10": "Paper_airplane"}, "fire_nation"), + (2, 2, {"p1": "prop12", "p2": 2, "p10": "Paper_ship"}, "air_nomads"), + (3, 1, {"p1": "shivam_kapoor", "p9": 5}, "fire_nation"), + (3, 3, {"p2": 6, "p3": 1, "p10": "Paper_airplane"}, "fire_nation"), + (4, 1, {"p1": "shivam_kapoor", "p9": 5}, "fire_nation"), + (3, 4, {"p4": "pometry"}, None), + (4, 4, {"p5": 12}, None), + ] + + for time, id, props, node_type in nodes: + graph.add_node(time, str(id), props, node_type) + + edge_data = [ + (1, "1", "2", {"p1": "shivam_kapoor", "p10": "Paper_airplane"}, "fire_nation"), + (2, "1", "2", {"p1": "shivam_kapoor", "p2": 4}, "fire_nation"), + (2, "2", "3", {"p1": "prop12", "p2": 2, "p10": "Paper_ship"}, "air_nomads"), + (3, "3", "1", {"p2": 6, "p3": 1}, "fire_nation"), + (3, "2", "1", {"p2": 6, "p3": 1, "p10": "Paper_airplane"}, None), + (4, "David Gilmour", "John Mayer", {"p2": 6, "p3": 1}, None), + (4, "John Mayer", "Jimmy Page", {"p2": 6, "p3": 1}, None), + ] + + for time, src, dst, props, edge_type in edge_data: + graph.add_edge(time, src, dst, props, edge_type) + + return graph + +def generate_graph_variants(init_graph=None): + init_graph = init_graph or default_init_graph + graph = init_graph(Graph()) + persistent_graph = init_graph(PersistentGraph()) + + tmpdir = tempfile.TemporaryDirectory() + atexit.register(tmpdir.cleanup) + path = f"{tmpdir.name}/graph" + + graph.to_disk_graph(path) + disk_graph = DiskGraphStorage.load_from_dir(path) + + return [ + graph, + persistent_graph, + disk_graph.to_events(), + disk_graph.to_persistent(), + ] diff --git a/python/tests/test_base_install/test_filters/semantics/conftest.py b/python/tests/test_base_install/test_filters/semantics/conftest.py new file mode 100644 index 0000000000..6b37cc071e --- /dev/null +++ b/python/tests/test_base_install/test_filters/semantics/conftest.py @@ -0,0 +1,244 @@ +from raphtory import Graph, PersistentGraph +from raphtory import DiskGraphStorage +import tempfile +import pytest +import shutil +import atexit + +def init_nodes_graph(graph): + nodes = [ + (6, "N1", {"p1": 2}), + (7, "N1", {"p1": 1}), + (6, "N2", {"p1": 1}), + (7, "N2", {"p1": 2}), + (8, "N3", {"p1": 1}), + (9, "N4", {"p1": 1}), + (5, "N5", {"p1": 1}), + (6, "N5", {"p1": 2}), + (5, "N6", {"p1": 1}), + (6, "N6", {"p1": 1}), + (3, "N7", {"p1": 1}), + (5, "N7", {"p1": 1}), + (3, "N8", {"p1": 1}), + (4, "N8", {"p1": 2}), + (2, "N9", {"p1": 2}), + (2, "N10", {"q1": 0}), + (2, "N10", {"p1": 3}), + (2, "N11", {"p1": 3}), + (2, "N11", {"q1": 0}), + (2, "N12", {"q1": 0}), + (3, "N12", {"p1": 3}), + (2, "N13", {"q1": 0}), + (3, "N13", {"p1": 3}), + (2, "N14", {"q1": 0}), + (2, "N15", {}), + ] + + for time, label, props in nodes: + graph.add_node(time, label, props) + + constant_properties = { + "N1": {"p1": 1}, + "N4": {"p1": 2}, + "N9": {"p1": 1}, + "N10": {"p1": 1}, + "N11": {"p1": 1}, + "N12": {"p1": 1}, + "N13": {"p1": 1}, + "N14": {"p1": 1}, + "N15": {"p1": 1}, + } + + for label, props in constant_properties.items(): + graph.node(label).add_constant_properties(props) + + edges = [ + (6, "N1", "N2", {"p1": 2}), + (7, "N1", "N2", {"p1": 1}), + (6, "N2", "N3", {"p1": 1}), + (7, "N2", "N3", {"p1": 2}), + (8, "N3", "N4", {"p1": 1}), + (9, "N4", "N5", {"p1": 1}), + (5, "N5", "N6", {"p1": 1}), + (6, "N5", "N6", {"p1": 2}), + (5, "N6", "N7", {"p1": 1}), + (6, "N6", "N7", {"p1": 1}), + (3, "N7", "N8", {"p1": 1}), + (5, "N7", "N8", {"p1": 1}), + (3, "N8", "N9", {"p1": 1}), + (4, "N8", "N9", {"p1": 2}), + (2, "N9", "N10", {"p1": 2}), + (2, "N10", "N11", {"q1": 0}), + (2, "N10", "N11", {"p1": 3}), + (2, "N11", "N12", {"p1": 3}), + (2, "N11", "N12", {"q1": 0}), + (2, "N12", "N13", {"q1": 0}), + (3, "N12", "N13", {"p1": 3}), + (2, "N13", "N14", {"q1": 0}), + (3, "N13", "N14", {"p1": 3}), + (2, "N14", "N15", {"q1": 0}), + (2, "N15", "N1", {}), + ] + + for time, src, dst, props in edges: + graph.add_edge(time, src, dst, props) + + constant_properties = [ + ("N1", "N2", {"p1": 1}), + ("N4", "N5", {"p1": 2}), + ("N9", "N10", {"p1": 1}), + ("N10", "N11", {"p1": 1}), + ("N11", "N12", {"p1": 1}), + ("N12", "N13", {"p1": 1}), + ("N13", "N14", {"p1": 1}), + ("N14", "N15", {"p1": 1}), + ("N15", "N1", {"p1": 1}), + ] + + for src, dst, props in constant_properties: + graph.edge(src, dst).add_constant_properties(props) + + return graph + +# For this graph there won't be any temporal property index for property name "p1". +def init_nodes_graph1(graph): + nodes = [ + (2, "N1", {"q1": 0}), + (2, "N2", {}), + ] + + for time, label, props in nodes: + graph.add_node(time, label, props) + + constant_properties = { + "N1": {"p1": 1}, + "N2": {"p1": 1}, + } + + for label, props in constant_properties.items(): + graph.node(label).add_constant_properties(props) + + return graph + +# For this graph there won't be any constant property index for property name "p1". +def init_nodes_graph2(graph): + nodes = [ + (1, "N1", {"p1": 1}), + + (2, "N2", {"p1": 1}), + (3, "N2", {"p1": 2}), + + (2, "N3", {"p1": 2}), + (3, "N3", {"p1": 1}), + + (2, "N4", {}), + ] + + for time, label, props in nodes: + graph.add_node(time, label, props) + + constant_properties = { + "N1": {"p2": 1}, + "N2": {"p1": 1}, + } + + for label, props in constant_properties.items(): + graph.node(label).add_constant_properties(props) + + return graph + + +def init_edges_graph(graph): + edges = [ + (6, "N1", "N2", {"p1": 2}), + (7, "N1", "N2", {"p1": 1}), + (6, "N2", "N3", {"p1": 1}), + (7, "N2", "N3", {"p1": 2}), + (8, "N3", "N4", {"p1": 1}), + (9, "N4", "N5", {"p1": 1}), + (5, "N5", "N6", {"p1": 1}), + (6, "N5", "N6", {"p1": 2}), + (5, "N6", "N7", {"p1": 1}), + (6, "N6", "N7", {"p1": 1}), + (3, "N7", "N8", {"p1": 1}), + (5, "N7", "N8", {"p1": 1}), + (3, "N8", "N9", {"p1": 1}), + (4, "N8", "N9", {"p1": 2}), + (2, "N9", "N10", {"p1": 2}), + (2, "N10", "N11", {"q1": 0}), + (2, "N10", "N11", {"p1": 3}), + (2, "N11", "N12", {"p1": 3}), + (2, "N11", "N12", {"q1": 0}), + (2, "N12", "N13", {"q1": 0}), + (3, "N12", "N13", {"p1": 3}), + (2, "N13", "N14", {"q1": 0}), + (3, "N13", "N14", {"p1": 3}), + (2, "N14", "N15", {"q1": 0}), + (2, "N15", "N1", {}), + ] + + for time, src, dst, props in edges: + graph.add_edge(time, src, dst, props) + + constant_properties = [ + ("N1", "N2", {"p1": 1}), + ("N4", "N5", {"p1": 2}), + ("N9", "N10", {"p1": 1}), + ("N10", "N11", {"p1": 1}), + ("N11", "N12", {"p1": 1}), + ("N12", "N13", {"p1": 1}), + ("N13", "N14", {"p1": 1}), + ("N14", "N15", {"p1": 1}), + ("N15", "N1", {"p1": 1}), + ] + + for src, dst, props in constant_properties: + graph.edge(src, dst).add_constant_properties(props) + + return graph + + +# For this graph there won't be any constant property index for property name "p1". +def init_edges_graph1(graph): + edges = [ + (2, "N1", "N2", {"q1": 0}), + (2, "N2", "N3", {}), + ] + + for time, src, dst, props in edges: + graph.add_edge(time, src, dst, props) + + constant_properties = [ + ("N1", "N2", {"p1": 1}), + ("N2", "N3", {"p1": 1}), + ] + + for src, dst, props in constant_properties: + graph.edge(src, dst).add_constant_properties(props) + + return graph + + +# For this graph there won't be any constant property index for property name "p1". +def init_edges_graph2(graph): + edges = [ + (2, "N1", "N2", {"p1": 1}), + (2, "N2", "N3", {"p1": 1}), + (2, "N2", "N3", {"p1": 2}), + (2, "N3", "N4", {"p1": 2}), + (2, "N3", "N4", {"p1": 1}), + (2, "N4", "N5", {}), + ] + + for time, src, dst, props in edges: + graph.add_edge(time, src, dst, props) + + constant_properties = [ + ("N1", "N2", {"p2": 1}), + ] + + for src, dst, props in constant_properties: + graph.edge(src, dst).add_constant_properties(props) + + return graph + diff --git a/python/tests/test_base_install/test_filters/semantics/test_edge_property_filter_semantics.py b/python/tests/test_base_install/test_filters/semantics/test_edge_property_filter_semantics.py new file mode 100644 index 0000000000..6eee521741 --- /dev/null +++ b/python/tests/test_base_install/test_filters/semantics/test_edge_property_filter_semantics.py @@ -0,0 +1,154 @@ +from raphtory import filter, Prop +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants +from test_base_install.test_filters.semantics.conftest import init_edges_graph, init_edges_graph1, init_edges_graph2 + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants(init_edges_graph) + +def init_graph_for_secondary_indexes(graph): + edges = [ + (1, "N16", "N15", {"p1": 2}), + (1, "N16", "N15", {"p1": 1}), + (1, "N17", "N16", {"p1": 1}), + (1, "N17", "N16", {"p1": 2}), + ] + + for time, src, dst, props in edges: + graph.add_edge(time, src, dst, props) + + return graph + + +# Disk graph doesn't have constant edge properties +@pytest.mark.parametrize("graph", [graph]) +def test_constant_semantics(graph): + filter_expr = filter.Property("p1").constant() == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N10","N11"), ("N11","N12"), ("N12","N13"), ("N13","N14"), ("N14","N15"), ("N15","N1"), ("N9","N10")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_temporal_any_semantics(graph): + filter_expr = filter.Property("p1").temporal().any() == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N2","N3"), ("N3","N4"), ("N4","N5"), ("N5","N6"), ("N6","N7"), ("N7","N8"),("N8","N9")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph]) +def test_temporal_any_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_edges_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1").temporal().any() == 1 + + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N16","N15"), ("N17","N16"), ("N2","N3"), ("N3","N4"), ("N4","N5"), ("N5","N6"), ("N6","N7"), ("N7","N8"), ("N8","N9")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph]) +def test_temporal_any_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1").temporal().any() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_temporal_latest_semantics(graph): + filter_expr = filter.Property("p1").temporal().latest() == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N3","N4"), ("N4","N5"), ("N6","N7"), ("N7","N8")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph]) +def test_temporal_latest_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_edges_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1").temporal().latest() == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N16","N15"), ("N3","N4"), ("N4","N5"), ("N6","N7"), ("N7","N8")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph]) +def test_temporal_latest_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1").temporal().latest() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +@pytest.mark.parametrize("graph", [graph]) +def test_property_semantics(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N14","N15"), ("N15","N1"), ("N3","N4"), ("N4","N5"), ("N6","N7"), ("N7","N8")]) + assert result_ids == expected_ids + + +# Disk graph doesn't have constant edge properties +@pytest.mark.parametrize("graph", [event_disk_graph]) +def test_property_semantics2(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N3","N4"), ("N4","N5"), ("N6","N7"), ("N7","N8")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph]) +def test_property_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_edges_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N14","N15"), ("N15","N1"), ("N16","N15"), ("N3","N4"), ("N4","N5"), ("N6","N7"), ("N7","N8")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph]) +def test_property_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +graph1, persistent_graph1, event_disk_graph1, persistent_disk_graph1 = generate_graph_variants(init_edges_graph1) + +@pytest.mark.parametrize("graph", [graph1]) +def test_property_semantics_only_constant(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N2","N3")]) + assert result_ids == expected_ids + +# Disk graph doesn't have constant edge properties +@pytest.mark.parametrize("graph", [event_disk_graph1]) +def test_property_semantics_only_constant2(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + +graph2, persistent_graph2, event_disk_graph2, persistent_disk_graph2 = generate_graph_variants(init_edges_graph2) + +@pytest.mark.parametrize("graph", [graph2, event_disk_graph2]) +def test_property_semantics_only_temporal(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("N1","N2"), ("N3","N4")]) + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/semantics/test_node_property_filter_semantics.py b/python/tests/test_base_install/test_filters/semantics/test_node_property_filter_semantics.py new file mode 100644 index 0000000000..1f13885a96 --- /dev/null +++ b/python/tests/test_base_install/test_filters/semantics/test_node_property_filter_semantics.py @@ -0,0 +1,133 @@ +from raphtory import filter, Prop +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants +from test_base_install.test_filters.semantics.conftest import init_nodes_graph, init_nodes_graph1, init_nodes_graph2 + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants(init_nodes_graph) + +def init_graph_for_secondary_indexes(graph): + graph.add_node(1, "N16", {"p1": 2}) + graph.add_node(1, "N16", {"p1": 1}) + + graph.add_node(1, "N17", {"p1": 1}) + graph.add_node(1, "N17", {"p1": 2}) + + return graph + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_constant_semantics(graph): + filter_expr = filter.Property("p1").constant() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_temporal_any_semantics(graph): + filter_expr = filter.Property("p1").temporal().any() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph, persistent_graph]) +def test_temporal_any_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_nodes_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1").temporal().any() == 1 + + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N16", "N17", "N2", "N3", "N4", "N5", "N6", "N7", "N8"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph, persistent_disk_graph]) +def test_temporal_any_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1").temporal().any() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_temporal_latest_semantics(graph): + filter_expr = filter.Property("p1").temporal().latest() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N3", "N4", "N6", "N7"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph, persistent_graph]) +def test_temporal_latest_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_nodes_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1").temporal().latest() == 1 + + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N16", "N3", "N4", "N6", "N7"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph, persistent_disk_graph]) +def test_temporal_latest_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1").temporal().latest() == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_property_semantics(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N14", "N15", "N3", "N4", "N6", "N7"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("base_graph", [graph, persistent_graph]) +def test_property_semantics_for_secondary_indexes(base_graph): + # Create a new graph using the same type as base_graph (Graph or PersistentGraph) + graph = type(base_graph)() + graph = init_nodes_graph(graph) + graph = init_graph_for_secondary_indexes(graph) + + filter_expr = filter.Property("p1") == 1 + + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N14", "N15", "N16", "N3", "N4", "N6", "N7"]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [event_disk_graph, persistent_disk_graph]) +def test_property_semantics_for_secondary_indexes_dsg(graph): + with pytest.raises(Exception, match="Immutable graph is .. immutable!"): + graph = init_graph_for_secondary_indexes(graph) + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + + +graph1, persistent_graph1, event_disk_graph1, persistent_disk_graph1 = generate_graph_variants(init_nodes_graph1) + +@pytest.mark.parametrize("graph", [graph1, persistent_graph1, event_disk_graph1, persistent_disk_graph1]) +def test_property_semantics_only_constant(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N2"]) + assert result_ids == expected_ids + +graph2, persistent_graph2, event_disk_graph2, persistent_disk_graph2 = generate_graph_variants(init_nodes_graph2) + +@pytest.mark.parametrize("graph", [graph2, persistent_graph2, event_disk_graph2, persistent_disk_graph2]) +def test_property_semantics_only_temporal(graph): + filter_expr = filter.Property("p1") == 1 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = sorted(["N1", "N3"]) + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/test_edge_composite_filter.py b/python/tests/test_base_install/test_filters/test_edge_composite_filter.py new file mode 100644 index 0000000000..660b128124 --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_edge_composite_filter.py @@ -0,0 +1,69 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_edge_composite_filter(graph): + filter_expr1 = filter.Property("p2") == 2 + filter_expr2 = filter.Property("p1") == "kapoor" + result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr1 = filter.Property("p2") > 2 + filter_expr2 = filter.Property("p1") == "shivam_kapoor" + result_ids = sorted(graph.filter_edges(filter_expr1 | filter_expr2).edges.id) + expected_ids = sorted([('1', '2'), ('2', '1'), ('3', '1'), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + +# TODO: Enable this test once string property is fixed for disk_storage_graph +# filter_expr1 = filter.Property("p2") < 9 +# filter_expr2 = filter.Property("p1") == "shivam_kapoor" +# result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) +# expected_ids = [("1", "2")] +# assert result_ids == expected_ids + + filter_expr1 = filter.Property("p2") < 9 + filter_expr2 = filter.Property("p3") < 9 + result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) + expected_ids = [("2", "1"), ("3", "1"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")] + assert result_ids == expected_ids + +# TODO: Enable this test once string property is fixed for disk_storage_graph +# filter_expr1 = filter.Edge.src().name() == "1" +# filter_expr2 = filter.Property("p1") == "shivam_kapoor" +# result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) +# expected_ids = [("1", "2")] +# assert result_ids == expected_ids + + filter_expr1 = filter.Edge.dst().name() == "1" + filter_expr2 = filter.Property("p2") <= 6 + result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) + expected_ids = sorted([('2', '1'), ('3', '1')]) + assert result_ids == expected_ids + +# TODO: Enable this test once string property is fixed for disk_storage_graph +# filter_expr1 = filter.Edge.src().name() == "1" +# filter_expr2 = filter.Property("p1") == "shivam_kapoor" +# filter_expr3 = filter.Property("p3") == 5 +# result_ids = sorted(graph.filter_edges((filter_expr1 & filter_expr2) | filter_expr3).edges.id) +# expected_ids = [("1", "2")] +# assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_not_edge_composite_filter(graph): + filter_expr1 = filter.Edge.dst().name() == "1" + filter_expr2 = filter.Property("p2") <= 2 + result_ids = sorted(graph.filter_edges(~filter_expr1 & filter_expr2).edges.id) + expected_ids = [('2', '3')] + assert result_ids == expected_ids + + filter_expr1 = filter.Edge.dst().name() == "1" + filter_expr2 = filter.Property("p2") <= 6 + result_ids = sorted(graph.filter_edges(~(filter_expr1 & filter_expr2)).edges.id) + expected_ids = sorted([('1', '2'), ('2', '3'), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/test_edge_filter.py b/python/tests/test_base_install/test_filters/test_edge_filter.py new file mode 100644 index 0000000000..2fc4c2d300 --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_edge_filter.py @@ -0,0 +1,131 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_eq(graph): + filter_expr = filter.Edge.src().name() == "2" + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("2", "3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_ne(graph): + filter_expr = filter.Edge.src().name() != "1" + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("2", "3"), ("3", "1"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_in(graph): + filter_expr = filter.Edge.src().name().is_in(["1"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [("1", "2")] + assert result_ids == expected_ids + + filter_expr = filter.Edge.src().name().is_in(["1", "2"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_not_in(graph): + filter_expr = filter.Edge.src().name().is_not_in(["1"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("2", "3"), ("3", "1"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_dst_eq(graph): + filter_expr = filter.Edge.dst().name() == "1" + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("3", "1")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_dst_ne(graph): + filter_expr = filter.Edge.dst().name() != "2" + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("2", "3"), ("3", "1"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_dst_in(graph): + filter_expr = filter.Edge.dst().name().is_in(["2"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [("1", "2")] + assert result_ids == expected_ids + + filter_expr = filter.Edge.dst().name().is_in(["2", "3"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_dst_not_in(graph): + filter_expr = filter.Edge.dst().name().is_not_in(["1"]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "3"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_edge_for_src_dst(graph): + filter_expr1 = filter.Edge.src().name() == "3" + filter_expr2 = filter.Edge.dst().name() == "1" + result_ids = sorted(graph.filter_edges(filter_expr1 & filter_expr2).edges.id) + expected_ids = [("3", "1")] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_contains(graph): + filter_expr = filter.Edge.src().name().contains("Mayer") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_src_not_contains(graph): + filter_expr = filter.Edge.src().name().not_contains("Mayer") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3"), ("3", "1"), ("David Gilmour", "John Mayer")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_fuzzy_search(graph): + filter_expr = filter.Edge.src().name().fuzzy_search("John", 2, True) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [("John Mayer", "Jimmy Page")] + assert result_ids == expected_ids + + filter_expr = filter.Edge.src().name().fuzzy_search("John", 2, False) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr = filter.Edge.dst().name().fuzzy_search("John May", 2, False) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('David Gilmour', 'John Mayer')] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_not_src(graph): + filter_expr = filter.Edge.src().name().not_contains("Mayer") + result_ids = sorted(graph.filter_edges(~filter_expr).edges.id) + expected_ids = [('John Mayer', 'Jimmy Page')] + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/test_edge_property_filter.py b/python/tests/test_base_install/test_filters/test_edge_property_filter.py new file mode 100644 index 0000000000..a98cc67134 --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_edge_property_filter.py @@ -0,0 +1,155 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_eq(graph): + filter_expr = filter.Property("p2") == 2 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_ne(graph): + filter_expr = filter.Property("p2") != 2 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_lt(graph): + filter_expr = filter.Property("p2") < 10 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_le(graph): + filter_expr = filter.Property("p2") <= 6 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_gt(graph): + filter_expr = filter.Property("p2") > 2 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_edges_for_property_ge(graph): + filter_expr = filter.Property("p2") >= 2 + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_in(graph): + filter_expr = filter.Property("p2").is_in([]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr = filter.Property("p2").is_in([0]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr = filter.Property("p2").is_in([6]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + filter_expr = filter.Property("p2").is_in([2, 6]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("2", "1"), ("2", "3"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_not_in(graph): + filter_expr = filter.Property("p2").is_not_in([6]) + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_is_some(graph): + filter_expr = filter.Property("p2").is_some() + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1", "2"), ("2", "1"), ("2", "3"), ("3", "1"), ('David Gilmour', 'John Mayer'), ('John Mayer', 'Jimmy Page')]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_is_none(graph): + filter_expr = filter.Property("p3").is_none() + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = sorted([("1","2"),("2","3")]) + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_contains(graph): + filter_expr = filter.Property("p10").contains("Paper") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1'), ('2','3')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().any().contains("Paper") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1'), ('2','3')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().latest().contains("Paper") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1'), ('2','3')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").constant().contains("Paper") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_property_not_contains(graph): + filter_expr = filter.Property("p10").not_contains("ship") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().any().not_contains("ship") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().latest().not_contains("ship") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [('1','2'), ('2','1')] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").constant().not_contains("ship") + result_ids = sorted(graph.filter_edges(filter_expr).edges.id) + expected_ids = [] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, event_disk_graph]) +def test_filter_edges_for_not_property(graph): + filter_expr = filter.Property("p3").is_none() + result_ids = sorted(graph.filter_edges(~filter_expr).edges.id) + expected_ids = sorted([("2","1"),("3","1"), ("David Gilmour", "John Mayer"), ("John Mayer", "Jimmy Page")]) + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/test_node_composite_filter.py b/python/tests/test_base_install/test_filters/test_node_composite_filter.py new file mode 100644 index 0000000000..e0e18c2062 --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_node_composite_filter.py @@ -0,0 +1,59 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_node_composite_filter(graph): + filter_expr1 = filter.Property("p2") == 2 + filter_expr2 = filter.Property("p1") == "kapoor" + result_ids = sorted(graph.filter_nodes(filter_expr1 & filter_expr2).nodes.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr1 = filter.Property("p2") > 2 + filter_expr2 = filter.Property("p1") == "shivam_kapoor" + result_ids = sorted(graph.filter_nodes(filter_expr1 | filter_expr2).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + + filter_expr1 = filter.Property("p9") < 9 + filter_expr2 = filter.Property("p1") == "shivam_kapoor" + result_ids = sorted(graph.filter_nodes(filter_expr1 & filter_expr2).nodes.id) + expected_ids = ["1"] + assert result_ids == expected_ids + + filter_expr1 = filter.Node.node_type() == "fire_nation" + filter_expr2 = filter.Property("p1") == "shivam_kapoor" + result_ids = sorted(graph.filter_nodes(filter_expr1 & filter_expr2).nodes.id) + expected_ids = ["1"] + assert result_ids == expected_ids + + filter_expr1 = filter.Node.name() == "2" + filter_expr2 = filter.Property("p2") >= 2 + result_ids = sorted(graph.filter_nodes(filter_expr1 & filter_expr2).nodes.id) + expected_ids = ["2"] + assert result_ids == expected_ids + + filter_expr1 = filter.Node.name() == "2" + filter_expr2 = filter.Property("p2") == 2 + filter_expr3 = filter.Property("p9") <= 5 + result_ids = sorted(graph.filter_nodes((filter_expr1 & filter_expr2) | filter_expr3).nodes.id) + expected_ids = ["1", "2"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_not_node_composite_filter(graph): + filter_expr1 = filter.Node.name() == "2" + filter_expr2 = filter.Property("p2") >= 2 + result_ids = sorted(graph.filter_nodes(~filter_expr1 & filter_expr2).nodes.id) + expected_ids = ["3"] + assert result_ids == expected_ids + + result_ids = sorted(graph.filter_nodes(~(filter_expr1 & filter_expr2)).nodes.id) + expected_ids = sorted(["1", "3", "4", "David Gilmour", "Jimmy Page", "John Mayer"]) + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_filters/test_node_filter.py b/python/tests/test_base_install/test_filters/test_node_filter.py new file mode 100644 index 0000000000..751e37b6ee --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_node_filter.py @@ -0,0 +1,122 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_name_eq(graph): + filter_expr = filter.Node.name() == "3" + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["3"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_name_ne(graph): + filter_expr = filter.Node.name() != "2" + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "3", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_name_in(graph): + filter_expr = filter.Node.name().is_in(["1"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1"] + assert result_ids == expected_ids + + filter_expr = filter.Node.name().is_in(["2", "3"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2", "3"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_name_not_in(graph): + filter_expr = filter.Node.name().is_not_in(["1"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2", "3", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_eq(graph): + filter_expr = filter.Node.node_type() == "fire_nation" + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_ne(graph): + filter_expr = filter.Node.node_type() != "fire_nation" + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_in(graph): + filter_expr = filter.Node.node_type().is_in(["fire_nation"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + + filter_expr = filter.Node.node_type().is_in(["fire_nation", "air_nomads"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "2", "3"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_not_in(graph): + filter_expr = filter.Node.node_type().is_not_in(["fire_nation"]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_contains(graph): + filter_expr = filter.Node.node_type().contains("fire") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_node_type_not_contains(graph): + filter_expr = filter.Node.node_type().not_contains("fire") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_fuzzy_search(graph): + filter_expr = filter.Node.node_type().fuzzy_search("fire", 2, True) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + + filter_expr = filter.Node.node_type().fuzzy_search("fire", 2, False) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = [] + assert result_ids == expected_ids + + filter_expr = filter.Node.node_type().fuzzy_search("air_noma", 2, False) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["2"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_not_node_type(graph): + filter_expr = filter.Node.node_type().is_not_in(["fire_nation"]) + result_ids = sorted(graph.filter_nodes(~filter_expr).nodes.id) + expected_ids = ["1", "3"] + assert result_ids == expected_ids + 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 new file mode 100644 index 0000000000..2b8f08be5b --- /dev/null +++ b/python/tests/test_base_install/test_filters/test_node_property_filter.py @@ -0,0 +1,159 @@ +from raphtory import Prop, filter +import pytest +from test_base_install.test_filters.conftest import generate_graph_variants + +graph, persistent_graph, event_disk_graph, persistent_disk_graph = generate_graph_variants() + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_eq(graph): + filter_expr = filter.Property("p2") == 2 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p1") == "shivam_kapoor" + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_ne(graph): + filter_expr = filter.Property("p2") != 2 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_lt(graph): + filter_expr = filter.Property("p2") < 10 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2', '3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_le(graph): + filter_expr = filter.Property("p2") <= 6 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2', '3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_gt(graph): + filter_expr = filter.Property("p2") > 2 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_nodes_for_property_ge(graph): + filter_expr = filter.Property("p2") >= 2 + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2', '3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_in(graph): + filter_expr = filter.Property("p2").is_in([6]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p2").is_in([2, 6]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2', '3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_not_in(graph): + filter_expr = filter.Property("p2").is_not_in([6]) + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_is_some(graph): + filter_expr = filter.Property("p2").is_some() + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['2', '3'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_is_none(graph): + filter_expr = filter.Property("p2").is_none() + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ["1", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_by_props_added_at_different_times(graph): + filter_expr1 = filter.Property("p4") == "pometry" + filter_expr2 = filter.Property("p5") == 12 + result_ids = sorted(graph.filter_nodes(filter_expr1 & filter_expr2).nodes.id) + expected_ids = ['4'] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_contains(graph): + filter_expr = filter.Property("p10").contains("Paper") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '2', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().any().contains("Paper") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '2', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().latest().contains("Paper") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '2', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").constant().contains("Paper") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = [] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_property_not_contains(graph): + filter_expr = filter.Property("p10").not_contains("ship") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().any().not_contains("ship") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").temporal().latest().not_contains("ship") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = ['1', '3'] + assert result_ids == expected_ids + + filter_expr = filter.Property("p10").constant().not_contains("ship") + result_ids = sorted(graph.filter_nodes(filter_expr).nodes.id) + expected_ids = [] + assert result_ids == expected_ids + + +@pytest.mark.parametrize("graph", [graph, persistent_graph, event_disk_graph, persistent_disk_graph]) +def test_filter_nodes_for_not_property(graph): + filter_expr = filter.Property("p2") > 2 + result_ids = sorted(graph.filter_nodes(~filter_expr).nodes.id) + expected_ids = ["1", "2", "4", "David Gilmour", "Jimmy Page", "John Mayer"] + assert result_ids == expected_ids + diff --git a/python/tests/test_base_install/test_graphdb/test_graphdb.py b/python/tests/test_base_install/test_graphdb/test_graphdb.py index 0c521d66a9..0b517612c4 100644 --- a/python/tests/test_base_install/test_graphdb/test_graphdb.py +++ b/python/tests/test_base_install/test_graphdb/test_graphdb.py @@ -56,26 +56,6 @@ def create_graph_with_deletions(): return g -if "DISK_TEST_MARK" in os.environ: - - def with_disk_graph(func): - def inner(graph): - def inner2(graph, tmpdirname): - g = graph.to_disk_graph(tmpdirname) - func(g) - - func(graph) - with tempfile.TemporaryDirectory() as tmpdirname: - inner2(graph, tmpdirname) - - return inner - -else: - - def with_disk_graph(func): - return func - - def test_graph_len_edge_len(): g = create_graph() diff --git a/python/tests/test_base_install/test_graphdb/test_property_filters.py b/python/tests/test_base_install/test_graphdb/test_property_filters.py index e8508a672a..7e7a83a539 100644 --- a/python/tests/test_base_install/test_graphdb/test_property_filters.py +++ b/python/tests/test_base_install/test_graphdb/test_property_filters.py @@ -1,8 +1,21 @@ from raphtory import Graph, PersistentGraph, Prop - +from raphtory import filter +import pytest def build_graph(): graph = Graph() + + graph.add_node(0, 1, {"node_str": "first", "node_int": 1}) + graph.add_node(1, 1, {"node_str": "second", "node_int": 2}) + graph.add_node(1, 2, {"node_str": "second", "node_int": 2}) + graph.add_node(2, 3, {"node_str": "third", "node_int": 3}) + graph.add_node(3, 4, {"node_str": "fourth", "node_int": 4, "node_bool": True}) + + graph.node(1).add_constant_properties({"c_prop1": "fire_nation"}) + graph.node(2).add_constant_properties({"c_prop1": "water_tribe"}) + graph.node(3).add_constant_properties({"c_prop1": "fire_nation"}) + graph.node(4).add_constant_properties({"c_prop1": "fire_nation"}) + graph.add_edge(0, 1, 2, {"test_str": "first", "test_int": 0}) graph.add_edge(1, 2, 3, {"test_str": "second", "test_int": 1}) graph.add_edge(2, 3, 4, {"test_int": 2}) @@ -10,112 +23,95 @@ def build_graph(): graph.add_edge(4, 2, 3, {"test_bool": True}) graph.add_edge(5, 2, 3, {"test_str": "third"}) - graph.add_node(0, 1, {"node_str": "first", "node_int": 1}) - graph.add_node(1, 2, {"node_str": "second", "node_int": 2}) - graph.add_node(2, 3, {"node_str": "third", "node_int": 3}) - graph.add_node(3, 4, {"node_str": "fourth", "node_int": 4, "node_bool": True}) + graph.edge(1, 2).add_constant_properties({"c_prop1": "water_tribe"}) + graph.edge(2, 3).add_constant_properties({"c_prop1": "water_tribe"}) + return graph -def test_filter_edges(): +def test_property_filter_nodes(): graph = build_graph() - assert graph.filter_edges(Prop("test_str") == "first").edges.id == [(1, 2)] - # is this the answer we want?, currently excludes edges that don't have the property - assert graph.filter_edges(Prop("test_str") != "first").edges.id == [(2, 3)] - assert graph.filter_edges(Prop("test_str").is_some()).edges.id == [(1, 2), (2, 3)] - assert graph.filter_edges(Prop("test_str").is_none()).edges.id == [(3, 4)] - assert graph.filter_edges(Prop("test_str") == "second").edges.id == [] - assert graph.before(5).filter_edges(Prop("test_str") == "second").edges.id == [ - (2, 3) - ] - assert graph.filter_edges(Prop("test_str").any({"first", "fourth"})).edges.id == [ - (1, 2) + test_node_cases = [ + (filter.Property("node_str") == "first", []), + (filter.Property("node_str") != "first", [1, 2, 3, 4]), + (filter.Property("node_bool").is_some(), [4]), + (filter.Property("node_bool").is_none(), [1, 2, 3]), + (filter.Property("node_int") == 2, [1, 2]), + (filter.Property("node_bool") == True, [4]), ] - assert graph.filter_edges(Prop("test_str").not_any({"first"})).edges.id == [ - (2, 3), + + for filter_expr, expected_ids in test_node_cases: + assert sorted(graph.filter_nodes(filter_expr).nodes.id) == sorted(expected_ids) + + test_edge_cases = [ + (filter.Property("node_str") == "first", []), + (filter.Property("node_str") != "first", [(1, 2), (2, 3), (3, 4)]), + (filter.Property("node_bool").is_none(), [(1, 2), (2, 3)]), + (filter.Property("node_int") != 1, [(1, 2), (2, 3), (3, 4)]), + (filter.Property("node_int") > 2, [(3, 4)]), + (filter.Property("node_int") >= 1, [(1, 2), (2, 3), (3, 4)]), + (filter.Property("node_int") < 3, [(1, 2)]), + (filter.Property("node_int") <= 2, [(1, 2)]), ] - assert ( - graph.filter_edges(Prop("test_int") == 2).edges.id == [] - ) # only looks at the latest value - assert graph.filter_edges(Prop("test_int") != 1).edges.id == [(1, 2), (3, 4)] - assert graph.filter_edges(Prop("test_int") > 2).edges.id == [(3, 4)] - assert graph.filter_edges(Prop("test_int") >= 1).edges.id == [(2, 3), (3, 4)] - assert graph.filter_edges(Prop("test_int") < 3).edges.id == [(1, 2), (2, 3)] - assert graph.filter_edges(Prop("test_int") <= 1).edges.id == [(1, 2), (2, 3)] - assert graph.filter_edges(Prop("test_bool") == True).edges.id == [ - (2, 3) - ] # worth adding special support for this? + for filter_expr, expected_ids in test_edge_cases: + assert sorted(graph.filter_nodes(filter_expr).edges.id) == sorted(expected_ids) -def test_filter_exploded_edges(): +def test_property_filter_edges(): graph = build_graph() - assert graph.filter_exploded_edges(Prop("test_str") == "first").edges.id == [(1, 2)] - # is this the answer we want?, currently excludes edges that don't have the property - assert graph.filter_exploded_edges(Prop("test_str") != "first").edges.id == [(2, 3)] - assert graph.filter_exploded_edges(Prop("test_str").is_some()).edges.id == [ - (1, 2), - (2, 3), - ] - assert graph.filter_exploded_edges(Prop("test_str").is_none()).edges.id == [ - (2, 3), - (3, 4), - ] - assert graph.filter_exploded_edges(Prop("test_str") == "second").edges.id == [ - (2, 3) - ] - assert graph.filter_exploded_edges( - Prop("test_str").any({"first", "fourth"}) - ).edges.id == [(1, 2)] - assert graph.filter_exploded_edges( - Prop("test_str").not_any({"first"}) - ).edges.id == [(2, 3)] - - assert graph.filter_exploded_edges(Prop("test_int") == 2).edges.id == [(3, 4)] - assert graph.filter_exploded_edges(Prop("test_int") != 2).edges.id == [ - (1, 2), - (2, 3), - (3, 4), - ] - assert graph.filter_exploded_edges(Prop("test_int") > 2).edges.id == [(3, 4)] - assert graph.filter_exploded_edges(Prop("test_int") >= 2).edges.id == [(3, 4)] - assert graph.filter_exploded_edges(Prop("test_int") < 3).edges.id == [ - (1, 2), - (2, 3), - (3, 4), - ] - assert graph.filter_exploded_edges(Prop("test_int") <= 1).edges.id == [ - (1, 2), - (2, 3), + test_cases = [ + (filter.Property("test_str") == "first", [(1, 2)]), + (filter.Property("test_str") != "first", [(2, 3)]), # currently excludes edges without the property + (filter.Property("test_str").is_some(), [(1, 2), (2, 3)]), + (filter.Property("test_str").is_none(), [(3, 4)]), + (filter.Property("test_str") == "second", []), + (filter.Property("test_str").is_in(["first", "fourth"]), [(1, 2)]), + (filter.Property("test_str").is_not_in(["first"]), [(2, 3)]), + + (filter.Property("test_int") == 2, []), + (filter.Property("test_int") != 1, [(1, 2), (3, 4)]), + (filter.Property("test_int") > 2, [(3, 4)]), + (filter.Property("test_int") >= 1, [(2, 3), (3, 4)]), + (filter.Property("test_int") < 3, [(1, 2), (2, 3)]), + (filter.Property("test_int") <= 1, [(1, 2), (2, 3)]), + + (filter.Property("test_bool") == True, [(2, 3)]), ] - assert graph.filter_exploded_edges(Prop("test_bool") == True).edges.id == [ - (2, 3) - ] # worth adding special support for this? + for filter_expr, expected_ids in test_cases: + assert sorted(graph.filter_edges(filter_expr).edges.id) == sorted(expected_ids) + # edge case: temporal filtering before time 5 + filter_expr = filter.Property("test_str") == "second" + expected_ids = [(2, 3)] + assert sorted(graph.before(5).filter_edges(filter_expr).edges.id) == sorted(expected_ids) -def test_filter_nodes(): + +@pytest.mark.skip(reason="Ignoring this test temporarily") +def test_filter_exploded_edges(): graph = build_graph() - assert graph.filter_nodes(Prop("node_str") == "first").nodes.id == [1] - assert graph.filter_nodes(Prop("node_str") == "first").edges.id == [] - assert graph.filter_nodes(Prop("node_str") != "first").nodes.id == [2, 3, 4] - assert graph.filter_nodes(Prop("node_str") != "first").edges.id == [(2, 3), (3, 4)] - assert graph.filter_nodes(Prop("node_bool").is_some()).nodes.id == [4] - assert graph.filter_nodes(Prop("node_bool").is_none()).edges.id == [(1, 2), (2, 3)] - assert graph.filter_nodes(Prop("node_int") == 2).nodes.id == [ - 2 - ] # only looks at the latest value - assert graph.filter_nodes(Prop("node_int") != 1).edges.id == [(2, 3), (3, 4)] - assert graph.filter_nodes(Prop("node_int") > 2).edges.id == [(3, 4)] - assert graph.filter_nodes(Prop("node_int") >= 1).edges.id == [ - (1, 2), - (2, 3), - (3, 4), + test_cases = [ + (Prop("test_str") == "first", [(1, 2)]), + (Prop("test_str") != "first", [(2, 3)]), # currently excludes edges without the property + (Prop("test_str").is_some(), [(1, 2), (2, 3)]), + (Prop("test_str").is_none(), [(2, 3), (3, 4)]), + (Prop("test_str") == "second", [(2, 3)]), + (Prop("test_str").is_in({"first", "fourth"}), [(1, 2)]), + (Prop("test_str").is_not_in({"first"}), [(2, 3)]), + + (Prop("test_int") == 2, [(3, 4)]), + (Prop("test_int") != 2, [(1, 2), (2, 3), (3, 4)]), + (Prop("test_int") > 2, [(3, 4)]), + (Prop("test_int") >= 2, [(3, 4)]), + (Prop("test_int") < 3, [(1, 2), (2, 3), (3, 4)]), + (Prop("test_int") <= 1, [(1, 2), (2, 3)]), + + (Prop("test_bool") == True, [(2, 3)]), # worth adding special support for this? ] - assert graph.filter_nodes(Prop("node_int") < 3).edges.id == [(1, 2)] - assert graph.filter_nodes(Prop("node_int") <= 2).edges.id == [(1, 2)] - assert graph.filter_nodes(Prop("node_bool") == True).nodes.id == [4] + for filter_expr, expected_ids in test_cases: + assert sorted(graph.filter_exploded_edges(filter_expr).edges.id) == sorted(expected_ids) diff --git a/python/tests/test_base_install/test_graphql/test_graph_file_time_stats.py b/python/tests/test_base_install/test_graphql/test_graph_file_time_stats.py index ea77293be5..6157f48258 100644 --- a/python/tests/test_base_install/test_graphql/test_graph_file_time_stats.py +++ b/python/tests/test_base_install/test_graphql/test_graph_file_time_stats.py @@ -26,6 +26,7 @@ def test_graph_file_time_stats(): gql_last_opened_time = result["graph"]["lastOpened"] gql_last_updated_time = result["graph"]["lastUpdated"] + graph_file_path = os.path.join(graph_file_path, "graph") file_stats = os.stat(graph_file_path) created_time_fs = file_stats.st_ctime * 1000 last_opened_time_fs = file_stats.st_atime * 1000 diff --git a/python/tests/test_base_install/test_graphql/test_graph_nodes_edges_property_filter.py b/python/tests/test_base_install/test_graphql/test_graph_nodes_edges_property_filter.py index b2c7213e21..60c1ec0992 100644 --- a/python/tests/test_base_install/test_graphql/test_graph_nodes_edges_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_graph_nodes_edges_property_filter.py @@ -120,13 +120,15 @@ def test_graph_node_property_filter_equal(graph): query = """ query { graph(path: "g") { - nodeFilter( - property: "prop5", - condition: { - operator: EQUAL, - value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } + nodeFilter( + filter: { + property: { + name: "prop5" + operator: EQUAL + value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } } - ) { + } + ) { nodes { list { name @@ -146,9 +148,11 @@ def test_graph_node_property_filter_equal_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop5", - condition: { - operator: EQUAL + filter: { + property: { + name: "prop5" + operator: EQUAL + } } ) { nodes { @@ -170,10 +174,12 @@ def test_graph_node_property_filter_equal_type_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop5", - condition: { - operator: EQUAL, - value: { i64: 1 } + filter: { + property: { + name: "prop5" + operator: EQUAL + value: { i64: 1 } + } } ) { nodes { @@ -195,10 +201,12 @@ def test_graph_node_property_filter_not_equal(graph): query { graph(path: "g") { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL, - value: { bool: true } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + value: { bool: true } + } } ) { nodes { @@ -222,10 +230,12 @@ def test_graph_node_property_filter_not_equal_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL - } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + } + } ) { nodes { list { @@ -246,11 +256,13 @@ def test_graph_node_property_filter_not_equal_type_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL, - value: { i64: 1 } - } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + value: { i64: 1 } + } + } ) { nodes { list { @@ -271,10 +283,12 @@ def test_graph_node_property_filter_greater_than_or_equal(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { i64: 60 } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + value: { i64: 60 } + } } ) { nodes { @@ -296,9 +310,11 @@ def test_graph_node_property_filter_greater_than_or_equal_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + } } ) { nodes { @@ -320,12 +336,14 @@ def test_graph_node_property_filter_greater_than_or_equal_type_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { bool: true } - } - ) { + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + value: { bool: true } + } + } + ) { nodes { list { name @@ -345,10 +363,12 @@ def test_graph_node_property_filter_less_than_or_equal(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { i64: 30 } + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + value: { i64: 30 } + } } ) { nodes { @@ -376,9 +396,11 @@ def test_graph_node_property_filter_less_than_or_equal_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + } } ) { nodes { @@ -399,13 +421,15 @@ def test_graph_node_property_filter_less_than_or_equal_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { str: "shivam" } - } - ) { + nodeFilter( + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + value: { str: "shivam" } + } + } + ) { nodes { list { name @@ -425,10 +449,12 @@ def test_graph_node_property_filter_greater_than(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN, - value: { i64: 30 } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + value: { i64: 30 } + } } ) { nodes { @@ -450,10 +476,12 @@ def test_graph_node_property_filter_greater_than_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN - } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + } + } ) { nodes { list { @@ -474,12 +502,14 @@ def test_graph_node_property_filter_greater_than_type_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + value: { str: "shivam" } + } } - ) { + ) { nodes { list { name @@ -499,11 +529,13 @@ def test_graph_node_property_filter_less_than(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN, - value: { i64: 30 } - } + filter: { + property: { + name: "prop1" + operator: LESS_THAN + value: { i64: 30 } + } + } ) { nodes { list { @@ -526,9 +558,11 @@ def test_graph_node_property_filter_less_than_no_value_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN + filter: { + property: { + name: "prop1" + operator: LESS_THAN + } } ) { nodes { @@ -550,12 +584,14 @@ def test_graph_node_property_filter_less_than_type_error(graph): query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN, - value: { str: "shivam" } - } - ) { + filter: { + property: { + name: "prop1" + operator: LESS_THAN + value: { str: "shivam" } + } + } + ) { nodes { list { name @@ -575,9 +611,11 @@ def test_graph_node_property_filter_is_none(graph): query { graph(path: "g") { nodeFilter( - property: "prop5", - condition: { - operator: IS_NONE + filter: { + property: { + name: "prop5" + operator: IS_NONE + } } ) { nodes { @@ -600,12 +638,14 @@ def test_graph_node_property_filter_is_some(graph): query = """ query { graph(path: "g") { - nodeFilter( - property: "prop5", - condition: { - operator: IS_SOME - } - ) { + nodeFilter( + filter: { + property: { + name: "prop5" + operator: IS_SOME + } + } + ) { nodes { list { name @@ -622,15 +662,17 @@ def test_graph_node_property_filter_is_some(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_any(graph): +def test_graph_node_property_filter_is_in(graph): query = """ query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + } } ) { nodes { @@ -649,18 +691,20 @@ def test_graph_node_property_filter_any(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_any_empty_list(graph): +def test_node_property_filter_is_in_empty_list(graph): query = """ query { graph(path: "g") { nodes { - nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { list: [] } - } - ) { + nodeFilter( + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: []} + } + } + ) { list { name } @@ -674,16 +718,19 @@ def test_node_property_filter_any_empty_list(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_any_no_value_error(graph): +def test_graph_node_property_filter_is_in_no_value(graph): query = """ query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - } - ) { + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: []} + } + } + ) { nodes { list { name @@ -693,20 +740,22 @@ def test_graph_node_property_filter_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_output = {"graph": {"nodeFilter": {"nodes": {"list": []}}}} + run_graphql_test(query, expected_output, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_any_type_error(graph): +def test_graph_node_property_filter_is_in_type_error(graph): query = """ query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { str: "shivam" } + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { str: "shivam" } + } } ) { nodes { @@ -718,22 +767,24 @@ def test_graph_node_property_filter_any_type_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" + expected_error_message = "PropertyType Error: Wrong type for property prop1: expected I64 but actual type is Str" run_graphql_error_test(query, expected_error_message, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_not_any(graph): +def test_graph_node_property_filter_is_not_in_any(graph): query = """ query { graph(path: "g") { - nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} - } - ) { + nodeFilter( + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + } + } + ) { nodes { list { name @@ -750,16 +801,18 @@ def test_graph_node_property_filter_not_any(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_not_any_empty_list(graph): +def test_node_property_filter_not_is_not_in_empty_list(graph): query = """ query { graph(path: "g") { nodes { - nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: { list: [] } + nodeFilter( + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { list: []} + } } ) { list { @@ -783,14 +836,16 @@ def test_node_property_filter_not_any_empty_list(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_not_any_no_value_error(graph): +def test_graph_node_property_filter_is_not_in_no_value_error(graph): query = """ query { graph(path: "g") { nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + } } ) { nodes { @@ -802,20 +857,52 @@ def test_graph_node_property_filter_not_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" + expected_error_message = "Expected a value for IsNotIn operator" run_graphql_error_test(query, expected_error_message, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_node_property_filter_not_any_type_error(graph): +def test_graph_node_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { - nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: { str: "shivam" } + nodeFilter( + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { str: "shivam" } + } + } + ) { + nodes { + list { + name + } + } + } + } + } + """ + expected_error_message = "PropertyType Error: Wrong type for property prop1: expected I64 but actual type is Str" + run_graphql_error_test(query, expected_error_message, graph()) + + +@pytest.mark.parametrize("graph", [Graph, PersistentGraph]) +def test_graph_node_not_property_filter(graph): + query = """ + query { + graph(path: "g") { + nodeFilter ( + filter: { + not: + { + property: { + name: "prop5" + operator: EQUAL + value: { list: [ {i64: 1}, {i64: 2} ] } + } + } } ) { nodes { @@ -827,8 +914,16 @@ def test_graph_node_property_filter_not_any_type_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_output = { + "graph": { + "nodeFilter": { + "nodes": { + "list": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] + } + } + } + } + run_graphql_test(query, expected_output, graph()) # Edge property filter is not supported yet for PersistentGraph @@ -838,10 +933,12 @@ def test_graph_edge_property_filter_equal(graph): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: EQUAL, - value: { list: [{i64: 1},{i64: 2},{i64: 3}]} + filter: { + property: { + name: "eprop5" + operator: EQUAL + value: { list: [{i64: 1},{i64: 2},{i64: 3}]} + } } ) { edges { @@ -868,13 +965,15 @@ def test_graph_edge_property_filter_equal_persistent_graph(): query = """ query { graph(path: "g") { - edgeFilter( - property: "eprop5", - condition: { - operator: EQUAL, - value: { list: [{i64: 1},{i64: 2},{i64: 3}]} - } - ) { + edgeFilter( + filter: { + property: { + name: "eprop5" + operator: EQUAL + value: { list: [{i64: 1},{i64: 2},{i64: 3}]} + } + } + ) { edges { list { src{name} @@ -895,9 +994,11 @@ def test_graph_edge_property_filter_equal_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: EQUAL + filter: { + property: { + name: "eprop5" + operator: EQUAL + } } ) { edges { @@ -921,12 +1022,14 @@ def test_graph_edge_property_filter_equal_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: EQUAL, - value: { i64: 1 } - } - ) { + filter: { + property: { + name: "eprop5" + operator: EQUAL + value: { i64: 1 } + } + } + ) { nodes { list { name @@ -945,10 +1048,12 @@ def test_graph_edge_property_filter_equal_type_error_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: EQUAL, - value: { i64: 1 } + filter: { + property: { + name: "eprop5" + operator: EQUAL + value: { i64: 1 } + } } ) { nodes { @@ -971,12 +1076,14 @@ def test_graph_edge_property_filter_not_equal(graph): query { graph(path: "g") { edgeFilter( - property: "eprop4", - condition: { - operator: NOT_EQUAL, - value: { bool: true } - } - ) { + filter: { + property: { + name: "eprop4" + operator: NOT_EQUAL + value: { bool: true } + } + } + ) { edges { list { src{name} @@ -1001,11 +1108,13 @@ def test_graph_edge_property_filter_not_equal_persistent_graph(): query = """ query { graph(path: "g") { - edgeFilter( - property: "eprop4", - condition: { - operator: NOT_EQUAL, - value: { bool: true } + edgeFilter( + filter: { + property: { + name: "eprop4" + operator: NOT_EQUAL + value: { bool: true } + } } ) { edges { @@ -1028,11 +1137,13 @@ def test_graph_edge_property_filter_not_equal_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop4", - condition: { - operator: NOT_EQUAL - } - ) { + filter: { + property: { + name: "eprop4" + operator: NOT_EQUAL + } + } + ) { edges { list { src{name} @@ -1054,10 +1165,12 @@ def test_graph_edge_property_filter_not_equal_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop4", - condition: { - operator: NOT_EQUAL, - value: { i64: 1 } + filter: { + property: { + name: "eprop4" + operator: NOT_EQUAL + value: { i64: 1 } + } } ) { edges { @@ -1079,12 +1192,14 @@ def test_graph_edge_property_filter_not_equal_type_error_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop4", - condition: { - operator: NOT_EQUAL, - value: { i64: 1 } - } - ) { + filter: { + property: { + name: "eprop4" + operator: NOT_EQUAL + value: { i64: 1 } + } + } + ) { edges { list { src{name} @@ -1106,10 +1221,12 @@ def test_graph_edge_property_filter_greater_than_or_equal(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { i64: 60 } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN_OR_EQUAL + value: { i64: 60 } + } } ) { edges { @@ -1137,12 +1254,14 @@ def test_graph_edge_property_filter_greater_than_or_equal_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { i64: 60 } - } - ) { + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN_OR_EQUAL + value: { i64: 60 } + } + } + ) { edges { list { src{name} @@ -1163,9 +1282,11 @@ def test_graph_edge_property_filter_greater_than_or_equal_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN_OR_EQUAL + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN_OR_EQUAL + } } ) { edges { @@ -1189,12 +1310,14 @@ def test_graph_edge_property_filter_greater_than_or_equal_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { bool: true } - } - ) { + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN_OR_EQUAL + value: { bool: true } + } + } + ) { edges { list { src{name} @@ -1214,10 +1337,12 @@ def test_graph_edge_property_filter_greater_than_or_equal_type_error_persistent_ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { bool: true } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN_OR_EQUAL + value: { bool: true } + } } ) { edges { @@ -1241,12 +1366,14 @@ def test_graph_edge_property_filter_less_than_or_equal(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { i64: 30 } - } - ) { + filter: { + property: { + name: "eprop1" + operator: LESS_THAN_OR_EQUAL + value: { i64: 30 } + } + } + ) { edges { list { src{name} @@ -1277,10 +1404,12 @@ def test_graph_edge_property_filter_less_than_or_equal_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { i64: 30 } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN_OR_EQUAL + value: { i64: 30 } + } } ) { edges { @@ -1303,9 +1432,11 @@ def test_graph_edge_property_filter_less_than_or_equal_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN_OR_EQUAL + filter: { + property: { + name: "eprop1" + operator: LESS_THAN_OR_EQUAL + } } ) { edges { @@ -1329,10 +1460,12 @@ def test_graph_edge_property_filter_less_than_or_equal_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN_OR_EQUAL + value: { str: "shivam" } + } } ) { edges { @@ -1354,10 +1487,12 @@ def test_graph_edge_property_filter_less_than_or_equal_type_error_persistent_gra query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN_OR_EQUAL + value: { str: "shivam" } + } } ) { edges { @@ -1381,10 +1516,12 @@ def test_graph_edge_property_filter_greater_than(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN, - value: { i64: 30 } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN + value: { i64: 30 } + } } ) { edges { @@ -1412,10 +1549,12 @@ def test_graph_edge_property_filter_greater_than_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN, - value: { i64: 30 } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN + value: { i64: 30 } + } } ) { edges { @@ -1438,9 +1577,11 @@ def test_graph_edge_property_filter_greater_than_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN + } } ) { edges { @@ -1464,10 +1605,12 @@ def test_graph_edge_property_filter_greater_than_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN + value: { str: "shivam" } + } } ) { edges { @@ -1489,10 +1632,12 @@ def test_graph_edge_property_filter_greater_than_type_error_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: GREATER_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: GREATER_THAN + value: { str: "shivam" } + } } ) { edges { @@ -1516,10 +1661,12 @@ def test_graph_edge_property_filter_less_than(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN, - value: { i64: 30 } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN + value: { i64: 30 } + } } ) { edges { @@ -1547,10 +1694,12 @@ def test_graph_edge_property_filter_less_than_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN, - value: { i64: 30 } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN + value: { i64: 30 } + } } ) { edges { @@ -1573,9 +1722,11 @@ def test_graph_edge_property_filter_less_than_no_value_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN + filter: { + property: { + name: "eprop1" + operator: LESS_THAN + } } ) { edges { @@ -1599,10 +1750,12 @@ def test_graph_edge_property_filter_less_than_type_error(graph): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN + value: { str: "shivam" } + } } ) { edges { @@ -1624,10 +1777,12 @@ def test_graph_edge_property_filter_less_than_type_error_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: LESS_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: LESS_THAN + value: { str: "shivam" } + } } ) { edges { @@ -1651,9 +1806,11 @@ def test_graph_edge_property_filter_is_none(graph): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: IS_NONE + filter: { + property: { + name: "eprop5" + operator: IS_NONE + } } ) { edges { @@ -1675,9 +1832,11 @@ def test_graph_edge_property_filter_is_none_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: IS_NONE + filter: { + property: { + name: "eprop5" + operator: IS_NONE + } } ) { edges { @@ -1701,9 +1860,11 @@ def test_graph_edge_property_filter_is_some(graph): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: IS_SOME + filter: { + property: { + name: "eprop5" + operator: IS_SOME + } } ) { edges { @@ -1737,9 +1898,11 @@ def test_graph_edge_property_filter_is_some_persistent_graph(): query { graph(path: "g") { edgeFilter( - property: "eprop5", - condition: { - operator: IS_SOME + filter: { + property: { + name: "eprop5" + operator: IS_SOME + } } ) { edges { @@ -1758,15 +1921,17 @@ def test_graph_edge_property_filter_is_some_persistent_graph(): # Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [Graph]) -def test_graph_edge_property_filter_any(graph): +def test_graph_edge_property_filter_is_in(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: ANY, - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + filter: { + property: { + name: "eprop1" + operator: IS_IN + value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + } } ) { edges { @@ -1794,15 +1959,17 @@ def test_graph_edge_property_filter_any(graph): run_graphql_test(query, expected_output, graph()) -def test_graph_edge_property_filter_any_persistent_graph(): +def test_graph_edge_property_filter_is_in_persistent_graph(): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: ANY, - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + filter: { + property: { + name: "eprop1" + operator: IS_IN + value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + } } ) { edges { @@ -1821,15 +1988,17 @@ def test_graph_edge_property_filter_any_persistent_graph(): # Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [Graph]) -def test_graph_edge_property_filter_any_empty_list(graph): +def test_graph_edge_property_filter_is_empty_list(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: ANY, - value: { list: [] } + filter: { + property: { + name: "eprop1" + operator: IS_IN + value: { list: []} + } } ) { edges { @@ -1846,15 +2015,17 @@ def test_graph_edge_property_filter_any_empty_list(graph): run_graphql_test(query, expected_output, graph()) -def test_graph_edge_property_filter_any_empty_list_persistent_graph(): +def test_graph_edge_property_filter_is_in_empty_list_persistent_graph(): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: ANY, - value: { list: [] } + filter: { + property: { + name: "eprop1" + operator: IS_IN + value: { list: []} + } } ) { edges { @@ -1872,14 +2043,16 @@ def test_graph_edge_property_filter_any_empty_list_persistent_graph(): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_edge_property_filter_any_no_value_error(graph): +def test_graph_edge_property_filter_is_in_no_value_error(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "prop1", - condition: { - operator: ANY, + filter: { + property: { + name: "prop1" + operator: IS_IN + } } ) { edges { @@ -1892,20 +2065,21 @@ def test_graph_edge_property_filter_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" + expected_error_message = "Expected a value for IsIn operator" run_graphql_error_test(query, expected_error_message, graph()) -@pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_edge_property_filter_any_type_error(graph): +def test_graph_edge_property_filter_is_in_type_error(): query = """ query { graph(path: "g") { edgeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: IS_IN + value: { str: "shivam" } + } } ) { edges { @@ -1918,21 +2092,50 @@ def test_graph_edge_property_filter_any_type_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_error_message = "PropertyType Error: Wrong type for property eprop1: expected I64 but actual type is Str" + run_graphql_error_test(query, expected_error_message, Graph()) + + +def test_graph_edge_property_filter_is_in_type_error_persistent_graph(): + query = """ + query { + graph(path: "g") { + edgeFilter( + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { str: "shivam" } + } + } + ) { + edges { + list { + src{name} + dst{name} + } + } + } + } + } + """ + expected_error_message = "Property filtering not implemented on PersistentGraph yet" + run_graphql_error_test(query, expected_error_message, PersistentGraph()) # Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [Graph]) -def test_graph_edge_property_filter_not_any(graph): +def test_graph_edge_property_filter_is_not_in(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + } } ) { edges { @@ -1955,15 +2158,17 @@ def test_graph_edge_property_filter_not_any(graph): run_graphql_test(query, expected_output, graph()) -def test_graph_edge_property_filter_not_any_persistent_graph(): +def test_graph_edge_property_filter_is_not_in_persistent_graph(): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, - value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { list: [{i64: 10},{i64: 20},{i64: 30}]} + } } ) { edges { @@ -1982,15 +2187,17 @@ def test_graph_edge_property_filter_not_any_persistent_graph(): # Edge property filter is not supported yet for PersistentGraph @pytest.mark.parametrize("graph", [Graph]) -def test_graph_edge_property_filter_not_any_empty_list(graph): +def test_graph_edge_property_filter_is_not_in_empty_list(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, - value: { list: [] } + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { list: []} + } } ) { edges { @@ -2019,15 +2226,17 @@ def test_graph_edge_property_filter_not_any_empty_list(graph): run_graphql_test(query, expected_output, graph()) -def test_graph_edge_property_filter_not_any_empty_list_persistent_graph(): +def test_graph_edge_property_filter_is_not_in_empty_list_persistent_graph(): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, - value: { list: [] } + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { list: []} + } } ) { edges { @@ -2045,14 +2254,16 @@ def test_graph_edge_property_filter_not_any_empty_list_persistent_graph(): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_edge_property_filter_not_any_no_value_error(graph): +def test_graph_edge_property_filter_is_not_in_no_value_error(graph): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + } } ) { edges { @@ -2065,20 +2276,21 @@ def test_graph_edge_property_filter_not_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" + expected_error_message = "Expected a value for IsNotIn operator" run_graphql_error_test(query, expected_error_message, graph()) -@pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_graph_edge_property_filter_not_any_type_error(graph): +def test_graph_edge_property_filter_is_not_in_type_error(): query = """ query { graph(path: "g") { edgeFilter( - property: "eprop1", - condition: { - operator: NOT_ANY, - value: { str: "shivam" } + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { str: "shivam" } + } } ) { edges { @@ -2091,5 +2303,74 @@ def test_graph_edge_property_filter_not_any_type_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_error_message = "PropertyType Error: Wrong type for property eprop1: expected I64 but actual type is Str" + run_graphql_error_test(query, expected_error_message, Graph()) + + +def test_graph_edge_property_filter_is_not_in_type_error_persistent_graph(): + query = """ + query { + graph(path: "g") { + edgeFilter( + filter: { + property: { + name: "eprop1" + operator: IS_NOT_IN + value: { str: "shivam" } + } + } + ) { + edges { + list { + src{name} + dst{name} + } + } + } + } + } + """ + expected_error_message = "Property filtering not implemented on PersistentGraph yet" + run_graphql_error_test(query, expected_error_message, PersistentGraph()) + + +def test_graph_edge_not_property_filter(): + query = """ + query { + graph(path: "g") { + edgeFilter ( + filter: { + not: + { + property: { + name: "eprop5" + operator: EQUAL + value: { list: [{i64: 1},{i64: 2}]} + } + } + } + ) { + edges { + list { + src{name} + dst{name} + } + } + } + } + } + """ + expected_output = { + "graph": { + "edgeFilter": { + "edges": { + "list": [ + {"dst": {"name": "d"}, "src": {"name": "a"}}, + {"dst": {"name": "d"}, "src": {"name": "b"}}, + {"dst": {"name": "d"}, "src": {"name": "c"}}, + ] + } + } + } + } + run_graphql_test(query, expected_output, Graph()) diff --git a/python/tests/test_base_install/test_graphql/test_nodes_property_filter.py b/python/tests/test_base_install/test_graphql/test_nodes_property_filter.py index bd154d8683..e76b302e92 100644 --- a/python/tests/test_base_install/test_graphql/test_nodes_property_filter.py +++ b/python/tests/test_base_install/test_graphql/test_nodes_property_filter.py @@ -122,12 +122,14 @@ def test_node_property_filter_equal(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop5", - condition: { - operator: EQUAL, - value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } - } - ) { + filter: { + property: { + name: "prop5" + operator: EQUAL + value: { list: [ {i64: 1}, {i64: 2}, {i64: 3} ] } + } + } + ) { list { name } @@ -147,9 +149,11 @@ def test_node_property_filter_equal_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop5", - condition: { - operator: EQUAL + filter: { + property: { + name: "prop5" + operator: EQUAL + } } ) { list { @@ -171,10 +175,12 @@ def test_node_property_filter_equal_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop5", - condition: { - operator: EQUAL, - value: { i64: 1 } + filter: { + property: { + name: "prop5" + operator: EQUAL + value: { i64: 1 } + } } ) { list { @@ -196,10 +202,12 @@ def test_node_property_filter_not_equal(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL, - value: { bool: true } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + value: { bool: true } + } } ) { list { @@ -223,10 +231,12 @@ def test_node_property_filter_not_equal_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL - } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + } + } ) { list { name @@ -247,11 +257,13 @@ def test_node_property_filter_not_equal_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop4", - condition: { - operator: NOT_EQUAL, - value: { i64: 1 } - } + filter: { + property: { + name: "prop4" + operator: NOT_EQUAL + value: { i64: 1 } + } + } ) { list { name @@ -272,10 +284,12 @@ def test_node_property_filter_greater_than_or_equal(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { i64: 60 } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + value: { i64: 60 } + } } ) { list { @@ -297,9 +311,11 @@ def test_node_property_filter_greater_than_or_equal_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + } } ) { list { @@ -321,12 +337,14 @@ def test_node_property_filter_greater_than_or_equal_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN_OR_EQUAL, - value: { bool: true } - } - ) { + filter: { + property: { + name: "prop1" + operator: GREATER_THAN_OR_EQUAL + value: { bool: true } + } + } + ) { list { name } @@ -346,10 +364,12 @@ def test_node_property_filter_less_than_or_equal(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { i64: 30 } + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + value: { i64: 30 } + } } ) { list { @@ -377,9 +397,11 @@ def test_node_property_filter_less_than_or_equal_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + } } ) { list { @@ -401,12 +423,14 @@ def test_node_property_filter_less_than_or_equal_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN_OR_EQUAL, - value: { str: "shivam" } - } - ) { + filter: { + property: { + name: "prop1" + operator: LESS_THAN_OR_EQUAL + value: { str: "shivam" } + } + } + ) { list { name } @@ -426,10 +450,12 @@ def test_node_property_filter_greater_than(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN, - value: { i64: 30 } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + value: { i64: 30 } + } } ) { list { @@ -451,10 +477,12 @@ def test_node_property_filter_greater_than_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN - } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + } + } ) { list { name @@ -475,12 +503,14 @@ def test_node_property_filter_greater_than_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: GREATER_THAN, - value: { str: "shivam" } + filter: { + property: { + name: "prop1" + operator: GREATER_THAN + value: { str: "shivam" } + } } - ) { + ) { list { name } @@ -500,11 +530,13 @@ def test_node_property_filter_less_than(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN, - value: { i64: 30 } - } + filter: { + property: { + name: "prop1" + operator: LESS_THAN + value: { i64: 30 } + } + } ) { list { name @@ -527,9 +559,11 @@ def test_node_property_filter_less_than_no_value_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN + filter: { + property: { + name: "prop1" + operator: LESS_THAN + } } ) { list { @@ -551,12 +585,14 @@ def test_node_property_filter_less_than_type_error(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: LESS_THAN, - value: { str: "shivam" } - } - ) { + filter: { + property: { + name: "prop1" + operator: LESS_THAN + value: { str: "shivam" } + } + } + ) { list { name } @@ -576,9 +612,11 @@ def test_node_property_filter_is_none(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop5", - condition: { - operator: IS_NONE + filter: { + property: { + name: "prop5" + operator: IS_NONE + } } ) { list { @@ -602,11 +640,13 @@ def test_node_property_filter_is_some(graph): graph(path: "g") { nodes { nodeFilter( - property: "prop5", - condition: { - operator: IS_SOME - } - ) { + filter: { + property: { + name: "prop5" + operator: IS_SOME + } + } + ) { list { name } @@ -622,16 +662,18 @@ def test_node_property_filter_is_some(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_any(graph): +def test_node_property_filter_is_in(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { list: [ {i64: 10}, {i64: 30}, {i64: 50}, {i64: 70} ] } + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + } } ) { list { @@ -649,16 +691,18 @@ def test_node_property_filter_any(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_any_empty_list(graph): +def test_node_property_filter_is_in_empty_list(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: { list: [] } + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: []} + } } ) { list { @@ -674,17 +718,20 @@ def test_node_property_filter_any_empty_list(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_any_no_value_error(graph): +def test_node_property_filter_is_in_no_value(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - } - ) { + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { list: [{i64: 100}]} + } + } + ) { list { name } @@ -693,21 +740,23 @@ def test_node_property_filter_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_output = {"graph": {"nodes": {"nodeFilter": {"list": []}} }} + run_graphql_test(query, expected_output, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_any_type_error(graph): +def test_node_property_filter_is_in_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: ANY, - value: {str: "shivam"} + filter: { + property: { + name: "prop1" + operator: IS_IN + value: { str: "shivam" } + } } ) { list { @@ -718,23 +767,25 @@ def test_node_property_filter_any_type_error(graph): } } """ - expected_error_message = "Expected a list for Any operator" + expected_error_message = "PropertyType Error: Wrong type for property prop1: expected I64 but actual type is Str" run_graphql_error_test(query, expected_error_message, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_not_any(graph): +def test_node_property_filter_is_not_in(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} - } - ) { + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { list: [{i64: 10},{i64: 30},{i64: 50},{i64: 70}]} + } + } + ) { list { name } @@ -750,18 +801,20 @@ def test_node_property_filter_not_any(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_not_any_empty_list(graph): +def test_node_property_filter_is_not_in_empty_list(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: { list: []} - } - ) { + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { list: []} + } + } + ) { list { name } @@ -783,15 +836,17 @@ def test_node_property_filter_not_any_empty_list(graph): @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_not_any_no_value_error(graph): +def test_node_property_filter_is_not_in_no_value_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + } } ) { list { @@ -802,23 +857,25 @@ def test_node_property_filter_not_any_no_value_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" + expected_error_message = "Expected a value for IsNotIn operator" run_graphql_error_test(query, expected_error_message, graph()) @pytest.mark.parametrize("graph", [Graph, PersistentGraph]) -def test_node_property_filter_not_any_type_error(graph): +def test_node_property_filter_is_not_in_type_error(graph): query = """ query { graph(path: "g") { nodes { nodeFilter( - property: "prop1", - condition: { - operator: NOT_ANY, - value: {str: "shivam"} - } - ) { + filter: { + property: { + name: "prop1" + operator: IS_NOT_IN + value: { str: "shivam" } + } + } + ) { list { name } @@ -827,5 +884,5 @@ def test_node_property_filter_not_any_type_error(graph): } } """ - expected_error_message = "Expected a list for NotAny operator" - run_graphql_error_test(query, expected_error_message, graph()) + expected_error_message = "PropertyType Error: Wrong type for property prop1: expected I64 but actual type is Str" + run_graphql_error_test(query, expected_error_message, graph()) \ No newline at end of file diff --git a/python/tests/test_base_install/test_index.py b/python/tests/test_base_install/test_index.py index f95115f05e..f8f67db61b 100644 --- a/python/tests/test_base_install/test_index.py +++ b/python/tests/test_base_install/test_index.py @@ -60,7 +60,7 @@ def init_graph(graph): def search_nodes(graph, filter_expr, limit=20, offset=0): - graph.create_index() + graph.create_index_in_ram() return sorted( [node.name for node in graph.search_nodes(filter_expr, limit, offset)] ) @@ -70,7 +70,7 @@ def test_search_nodes_for_node_name_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_name() == "N1" + filter_expr = filter.Node.name() == "N1" results = search_nodes(g, filter_expr) assert ["N1"] == results @@ -79,7 +79,7 @@ def test_search_nodes_for_node_name_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_name() != "N1" + filter_expr = filter.Node.name() != "N1" results = search_nodes(g, filter_expr) assert [ "N10", @@ -99,20 +99,20 @@ def test_search_nodes_for_node_name_ne(): ] == results -def test_search_nodes_for_node_name_includes(): +def test_search_nodes_for_node_name_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_name().includes(["N1", "N9"]) + filter_expr = filter.Node.name().is_in(["N1", "N9"]) results = search_nodes(g, filter_expr) assert ["N1", "N9"] == results -def test_search_nodes_for_node_name_excludes(): +def test_search_nodes_for_node_name_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_name().excludes(["N10", "N11", "N12", "N13", "N14"]) + filter_expr = filter.Node.name().is_not_in(["N10", "N11", "N12", "N13", "N14"]) results = search_nodes(g, filter_expr) assert ["N1", "N15", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9"] == results @@ -121,7 +121,7 @@ def test_search_nodes_for_node_name_fuzzy_match(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_name().fuzzy_search("1", 1, False) + filter_expr = filter.Node.name().fuzzy_search("1", 1, False) results = search_nodes(g, filter_expr) assert ["N1"] == results @@ -144,20 +144,20 @@ def test_search_nodes_for_node_type_ne(): assert ["N1", "N10", "N11", "N12", "N13", "N2", "N6", "N7", "N8", "N9"] == results -def test_search_nodes_for_node_type_includes(): +def test_search_nodes_for_node_type_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_type().includes(["air_nomads", "fire_nation"]) + filter_expr = filter.Node.node_type().is_in(["air_nomads", "fire_nation"]) results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N6", "N7", "N8"] == results -def test_search_nodes_for_node_type_excludes(): +def test_search_nodes_for_node_type_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_type().excludes( + filter_expr = filter.Node.node_type().is_not_in( ["water_tribe", "air_nomads", "fire_nation"] ) results = search_nodes(g, filter_expr) @@ -168,7 +168,7 @@ def test_search_nodes_for_node_type_fuzzy_match(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.node_type().fuzzy_search("air", 1, False) + filter_expr = filter.Node.node_type().fuzzy_search("air_nomad", 1, False) results = search_nodes(g, filter_expr) assert ["N12", "N13", "N7", "N8"] == results @@ -177,7 +177,7 @@ def test_search_nodes_for_property_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1") == 1 + filter_expr = filter.Property("p1") == 1 results = search_nodes(g, filter_expr) assert ["N1", "N14", "N15", "N3", "N4", "N6", "N7"] == results @@ -186,7 +186,7 @@ def test_search_nodes_for_property_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1") != 2 + filter_expr = filter.Property("p1") != 2 results = search_nodes(g, filter_expr) assert [ "N1", @@ -207,7 +207,7 @@ def test_search_nodes_for_property_lt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("q1") < 2 + filter_expr = filter.Property("q1") < 2 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N14"] == results @@ -216,7 +216,7 @@ def test_search_nodes_for_property_le(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("q1") <= 3 + filter_expr = filter.Property("q1") <= 3 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N14"] == results @@ -225,7 +225,7 @@ def test_search_nodes_for_property_gt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1") > 2 + filter_expr = filter.Property("p1") > 2 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13"] == results @@ -234,25 +234,25 @@ def test_search_nodes_for_property_ge(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1") >= 2 + filter_expr = filter.Property("p1") >= 2 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_includes(): +def test_search_nodes_for_property_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").includes([2]) + filter_expr = filter.Property("p1").is_in([2]) results = search_nodes(g, filter_expr) assert ["N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_excludes(): +def test_search_nodes_for_property_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").excludes([2]) + filter_expr = filter.Property("p1").is_not_in([2]) results = search_nodes(g, filter_expr) assert [ "N1", @@ -273,7 +273,7 @@ def test_search_nodes_for_property_is_some(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").is_some() + filter_expr = filter.Property("p1").is_some() results = search_nodes(g, filter_expr) assert [ "N1", @@ -299,7 +299,7 @@ def test_search_nodes_for_property_is_none(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").is_none() + filter_expr = filter.Property("p1").is_none() results = search_nodes(g, filter_expr) assert [] == results @@ -308,7 +308,7 @@ def test_search_nodes_for_property_constant_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() == 1 + filter_expr = filter.Property("p1").constant() == 1 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"] == results @@ -317,7 +317,7 @@ def test_search_nodes_for_property_constant_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() != 2 + filter_expr = filter.Property("p1").constant() != 2 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"] == results @@ -326,7 +326,7 @@ def test_search_nodes_for_property_constant_lt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() < 2 + filter_expr = filter.Property("p1").constant() < 2 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"] == results @@ -335,7 +335,7 @@ def test_search_nodes_for_property_constant_le(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() <= 3 + filter_expr = filter.Property("p1").constant() <= 3 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N4", "N9"] == results @@ -344,7 +344,7 @@ def test_search_nodes_for_property_constant_gt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() > 1 + filter_expr = filter.Property("p1").constant() > 1 results = search_nodes(g, filter_expr) assert ["N4"] == results @@ -353,25 +353,25 @@ def test_search_nodes_for_property_constant_ge(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant() >= 2 + filter_expr = filter.Property("p1").constant() >= 2 results = search_nodes(g, filter_expr) assert ["N4"] == results -def test_search_nodes_for_property_constant_includes(): +def test_search_nodes_for_property_constant_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant().includes([2]) + filter_expr = filter.Property("p1").constant().is_in([2]) results = search_nodes(g, filter_expr) assert ["N4"] == results -def test_search_nodes_for_property_constant_excludes(): +def test_search_nodes_for_property_constant_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant().excludes([2]) + filter_expr = filter.Property("p1").constant().is_not_in([2]) results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"] == results @@ -380,7 +380,7 @@ def test_search_nodes_for_property_constant_is_some(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant().is_some() + filter_expr = filter.Property("p1").constant().is_some() results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N4", "N9"] == results @@ -390,7 +390,7 @@ def test_search_nodes_for_property_constant_is_none(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").constant().is_none() + filter_expr = filter.Property("p1").constant().is_none() results = search_nodes(g, filter_expr) assert [] == results @@ -399,7 +399,7 @@ def test_search_nodes_for_property_temporal_any_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() == 1 + filter_expr = filter.Property("p1").temporal().any() == 1 results = search_nodes(g, filter_expr) assert ["N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8"] == results @@ -408,7 +408,7 @@ def test_search_nodes_for_property_temporal_any_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() != 2 + filter_expr = filter.Property("p1").temporal().any() != 2 results = search_nodes(g, filter_expr) assert [ "N1", @@ -430,7 +430,7 @@ def test_search_nodes_for_property_temporal_any_lt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() < 2 + filter_expr = filter.Property("p1").temporal().any() < 2 results = search_nodes(g, filter_expr) assert ["N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8"] == results @@ -439,7 +439,7 @@ def test_search_nodes_for_property_temporal_any_le(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() <= 3 + filter_expr = filter.Property("p1").temporal().any() <= 3 results = search_nodes(g, filter_expr) assert [ "N1", @@ -462,7 +462,7 @@ def test_search_nodes_for_property_temporal_any_gt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() > 1 + filter_expr = filter.Property("p1").temporal().any() > 1 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results @@ -471,25 +471,25 @@ def test_search_nodes_for_property_temporal_any_ge(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any() >= 2 + filter_expr = filter.Property("p1").temporal().any() >= 2 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_any_includes(): +def test_search_nodes_for_property_temporal_any_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any().includes([2]) + filter_expr = filter.Property("p1").temporal().any().is_in([2]) results = search_nodes(g, filter_expr) assert ["N1", "N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_any_excludes(): +def test_search_nodes_for_property_temporal_any_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any().excludes([2]) + filter_expr = filter.Property("p1").temporal().any().is_not_in([2]) results = search_nodes(g, filter_expr) assert [ "N1", @@ -511,7 +511,7 @@ def test_search_nodes_for_property_temporal_any_is_some(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any().is_some() + filter_expr = filter.Property("p1").temporal().any().is_some() results = search_nodes(g, filter_expr) assert [ "N1", @@ -535,7 +535,7 @@ def test_search_nodes_for_property_temporal_any_is_none(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().any().is_none() + filter_expr = filter.Property("p1").temporal().any().is_none() results = search_nodes(g, filter_expr) assert [] == results @@ -544,7 +544,7 @@ def test_search_nodes_for_property_temporal_latest_eq(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() == 1 + filter_expr = filter.Property("p1").temporal().latest() == 1 results = search_nodes(g, filter_expr) assert ["N1", "N3", "N4", "N6", "N7"] == results @@ -553,7 +553,7 @@ def test_search_nodes_for_property_temporal_latest_ne(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() != 2 + filter_expr = filter.Property("p1").temporal().latest() != 2 results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N3", "N4", "N6", "N7"] == results @@ -562,7 +562,7 @@ def test_search_nodes_for_property_temporal_latest_lt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() < 2 + filter_expr = filter.Property("p1").temporal().latest() < 2 results = search_nodes(g, filter_expr) assert ["N1", "N3", "N4", "N6", "N7"] == results @@ -571,7 +571,7 @@ def test_search_nodes_for_property_temporal_latest_le(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() <= 3 + filter_expr = filter.Property("p1").temporal().latest() <= 3 results = search_nodes(g, filter_expr) assert [ "N1", @@ -594,7 +594,7 @@ def test_search_nodes_for_property_temporal_latest_gt(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() > 1 + filter_expr = filter.Property("p1").temporal().latest() > 1 results = search_nodes(g, filter_expr) assert ["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"] == results @@ -603,25 +603,25 @@ def test_search_nodes_for_property_temporal_latest_ge(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest() >= 2 + filter_expr = filter.Property("p1").temporal().latest() >= 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_includes(): +def test_search_nodes_for_property_temporal_latest_is_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().includes([2]) + filter_expr = filter.Property("p1").temporal().latest().is_in([2]) results = search_nodes(g, filter_expr) assert ["N2", "N5", "N8", "N9"] == results -def test_search_nodes_for_property_temporal_latest_excludes(): +def test_search_nodes_for_property_temporal_latest_is_not_in(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().excludes([2]) + filter_expr = filter.Property("p1").temporal().latest().is_not_in([2]) results = search_nodes(g, filter_expr) assert ["N1", "N10", "N11", "N12", "N13", "N3", "N4", "N6", "N7"] == results @@ -630,7 +630,7 @@ def test_search_nodes_for_property_temporal_latest_is_some(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_some() + filter_expr = filter.Property("p1").temporal().latest().is_some() results = search_nodes(g, filter_expr) assert [ "N1", @@ -654,7 +654,7 @@ def test_search_nodes_for_composite_filter(): g = init_graph(g) filter1 = filter.Node.node_type() == "fire_nation" - filter2 = filter.Node.property("p1").constant() > 1 + filter2 = filter.Property("p1").constant() > 1 results = search_nodes(g, filter1 | filter2) assert ["N1", "N10", "N11", "N4", "N6"] == results @@ -664,7 +664,7 @@ def test_search_nodes_for_property_temporal_latest_is_none(): g = Graph() g = init_graph(g) - filter_expr = filter.Node.property("p1").temporal().latest().is_none() + filter_expr = filter.Property("p1").temporal().latest().is_none() results = search_nodes(g, filter_expr) assert [] == results @@ -726,7 +726,7 @@ def init_edges_graph(graph): def search_edges(graph, filter_expr, limit=20, offset=0): - graph.create_index() + graph.create_index_in_ram() return sorted( [ (edge.src.name, edge.dst.name) @@ -739,7 +739,7 @@ def test_search_edges_for_src_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.src() == "N1" + filter_expr = filter.Edge.src().name() == "N1" results = search_edges(g, filter_expr) assert [("N1", "N2")] == results @@ -748,7 +748,7 @@ def test_search_edges_for_src_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.src() != "N1" + filter_expr = filter.Edge.src().name() != "N1" results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -768,20 +768,20 @@ def test_search_edges_for_src_ne(): ] == results -def test_search_edges_for_src_includes(): +def test_search_edges_for_src_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.src().includes(["N1", "N9"]) + filter_expr = filter.Edge.src().name().is_in(["N1", "N9"]) results = search_edges(g, filter_expr) assert [("N1", "N2"), ("N9", "N10")] == results -def test_search_edges_for_src_excludes(): +def test_search_edges_for_src_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.src().excludes(["N10", "N11", "N12", "N13", "N14"]) + filter_expr = filter.Edge.src().name().is_not_in(["N10", "N11", "N12", "N13", "N14"]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -801,7 +801,7 @@ def test_search_edges_for_src_fuzzy_match(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.src().fuzzy_search("1", 1, False) + filter_expr = filter.Edge.src().name().fuzzy_search("1", 1, False) results = search_edges(g, filter_expr) assert [("N1", "N2")] == results @@ -810,7 +810,7 @@ def test_search_edges_for_dst_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.dst() == "N1" + filter_expr = filter.Edge.dst().name() == "N1" results = search_edges(g, filter_expr) assert [("N15", "N1")] == results @@ -819,7 +819,7 @@ def test_search_edges_for_dst_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.dst() != "N1" + filter_expr = filter.Edge.dst().name() != "N1" results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -839,20 +839,20 @@ def test_search_edges_for_dst_ne(): ] == results -def test_search_edges_for_dst_includes(): +def test_search_edges_for_dst_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.dst().includes(["N1", "N9"]) + filter_expr = filter.Edge.dst().name().is_in(["N1", "N9"]) results = search_edges(g, filter_expr) assert [("N15", "N1"), ("N8", "N9")] == results -def test_search_edges_for_dst_excludes(): +def test_search_edges_for_dst_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.dst().excludes(["N1", "N9", "N10"]) + filter_expr = filter.Edge.dst().name().is_not_in(["N1", "N9", "N10"]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -874,7 +874,7 @@ def test_search_edges_for_dst_fuzzy_match(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.dst().fuzzy_search("1", 1, False) + filter_expr = filter.Edge.dst().name().fuzzy_search("1", 1, False) results = search_edges(g, filter_expr) assert [("N15", "N1")] == results @@ -883,7 +883,7 @@ def test_search_edges_for_property_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1") == 1 + filter_expr = filter.Property("p1") == 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -900,7 +900,7 @@ def test_search_edges_for_property_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1") != 2 + filter_expr = filter.Property("p1") != 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -921,7 +921,7 @@ def test_search_edges_for_property_lt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("q1") < 2 + filter_expr = filter.Property("q1") < 2 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -936,7 +936,7 @@ def test_search_edges_for_property_le(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("q1") <= 3 + filter_expr = filter.Property("q1") <= 3 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -951,7 +951,7 @@ def test_search_edges_for_property_gt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1") > 2 + filter_expr = filter.Property("p1") > 2 results = search_edges(g, filter_expr) assert [("N10", "N11"), ("N11", "N12"), ("N12", "N13"), ("N13", "N14")] == results @@ -960,7 +960,7 @@ def test_search_edges_for_property_ge(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1") >= 2 + filter_expr = filter.Property("p1") >= 2 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -974,20 +974,20 @@ def test_search_edges_for_property_ge(): ] == results -def test_search_edges_for_property_includes(): +def test_search_edges_for_property_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").includes([2]) + filter_expr = filter.Property("p1").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_excludes(): +def test_search_edges_for_property_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").excludes([2]) + filter_expr = filter.Property("p1").is_not_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1008,7 +1008,7 @@ def test_search_edges_for_property_is_some(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").is_some() + filter_expr = filter.Property("p1").is_some() results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1034,7 +1034,7 @@ def test_search_edges_for_property_is_none(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").is_none() + filter_expr = filter.Property("p1").is_none() results = search_edges(g, filter_expr) assert [] == results @@ -1043,7 +1043,7 @@ def test_search_edges_for_property_constant_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() == 1 + filter_expr = filter.Property("p1").constant() == 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1061,7 +1061,7 @@ def test_search_edges_for_property_constant_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() != 2 + filter_expr = filter.Property("p1").constant() != 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1079,7 +1079,7 @@ def test_search_edges_for_property_constant_lt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() < 2 + filter_expr = filter.Property("p1").constant() < 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1097,7 +1097,7 @@ def test_search_edges_for_property_constant_le(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() <= 3 + filter_expr = filter.Property("p1").constant() <= 3 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1116,7 +1116,7 @@ def test_search_edges_for_property_constant_gt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() > 1 + filter_expr = filter.Property("p1").constant() > 1 results = search_edges(g, filter_expr) assert [("N4", "N5")] == results @@ -1125,25 +1125,25 @@ def test_search_edges_for_property_constant_ge(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant() >= 2 + filter_expr = filter.Property("p1").constant() >= 2 results = search_edges(g, filter_expr) assert [("N4", "N5")] == results -def test_search_edges_for_property_constant_includes(): +def test_search_edges_for_property_constant_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant().includes([2]) + filter_expr = filter.Property("p1").constant().is_in([2]) results = search_edges(g, filter_expr) assert [("N4", "N5")] == results -def test_search_edges_for_property_constant_excludes(): +def test_search_edges_for_property_constant_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant().excludes([2]) + filter_expr = filter.Property("p1").constant().is_not_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1161,7 +1161,7 @@ def test_search_edges_for_property_constant_is_some(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant().is_some() + filter_expr = filter.Property("p1").constant().is_some() results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1181,7 +1181,7 @@ def test_search_edges_for_property_constant_is_none(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").constant().is_none() + filter_expr = filter.Property("p1").constant().is_none() results = search_edges(g, filter_expr) assert [] == results @@ -1190,7 +1190,7 @@ def test_search_edges_for_property_temporal_any_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() == 1 + filter_expr = filter.Property("p1").temporal().any() == 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1208,7 +1208,7 @@ def test_search_edges_for_property_temporal_any_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() != 2 + filter_expr = filter.Property("p1").temporal().any() != 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1230,7 +1230,7 @@ def test_search_edges_for_property_temporal_any_lt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() < 2 + filter_expr = filter.Property("p1").temporal().any() < 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1248,7 +1248,7 @@ def test_search_edges_for_property_temporal_any_le(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() <= 3 + filter_expr = filter.Property("p1").temporal().any() <= 3 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1271,7 +1271,7 @@ def test_search_edges_for_property_temporal_any_gt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() > 1 + filter_expr = filter.Property("p1").temporal().any() > 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1290,7 +1290,7 @@ def test_search_edges_for_property_temporal_any_ge(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any() >= 2 + filter_expr = filter.Property("p1").temporal().any() >= 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1305,11 +1305,11 @@ def test_search_edges_for_property_temporal_any_ge(): ] == results -def test_search_edges_for_property_temporal_any_includes(): +def test_search_edges_for_property_temporal_any_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any().includes([2]) + filter_expr = filter.Property("p1").temporal().any().is_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1320,11 +1320,11 @@ def test_search_edges_for_property_temporal_any_includes(): ] == results -def test_search_edges_for_property_temporal_any_excludes(): +def test_search_edges_for_property_temporal_any_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any().excludes([2]) + filter_expr = filter.Property("p1").temporal().any().is_not_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1346,7 +1346,7 @@ def test_search_edges_for_property_temporal_any_is_some(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any().is_some() + filter_expr = filter.Property("p1").temporal().any().is_some() results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1370,7 +1370,7 @@ def test_search_edges_for_property_temporal_any_is_none(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().any().is_none() + filter_expr = filter.Property("p1").temporal().any().is_none() results = search_edges(g, filter_expr) assert [] == results @@ -1379,7 +1379,7 @@ def test_search_edges_for_property_temporal_latest_eq(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() == 1 + filter_expr = filter.Property("p1").temporal().latest() == 1 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1394,7 +1394,7 @@ def test_search_edges_for_property_temporal_latest_ne(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() != 2 + filter_expr = filter.Property("p1").temporal().latest() != 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1413,7 +1413,7 @@ def test_search_edges_for_property_temporal_latest_lt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() < 2 + filter_expr = filter.Property("p1").temporal().latest() < 2 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1428,7 +1428,7 @@ def test_search_edges_for_property_temporal_latest_le(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() <= 3 + filter_expr = filter.Property("p1").temporal().latest() <= 3 results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1451,7 +1451,7 @@ def test_search_edges_for_property_temporal_latest_gt(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() > 1 + filter_expr = filter.Property("p1").temporal().latest() > 1 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -1469,7 +1469,7 @@ def test_search_edges_for_property_temporal_latest_ge(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest() >= 2 + filter_expr = filter.Property("p1").temporal().latest() >= 2 results = search_edges(g, filter_expr) assert [ ("N10", "N11"), @@ -1483,20 +1483,20 @@ def test_search_edges_for_property_temporal_latest_ge(): ] == results -def test_search_edges_for_property_temporal_latest_includes(): +def test_search_edges_for_property_temporal_latest_is_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().includes([2]) + filter_expr = filter.Property("p1").temporal().latest().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_excludes(): +def test_search_edges_for_property_temporal_latest_is_not_in(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().excludes([2]) + filter_expr = filter.Property("p1").temporal().latest().is_not_in([2]) results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1515,7 +1515,7 @@ def test_search_edges_for_property_temporal_latest_is_some(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_some() + filter_expr = filter.Property("p1").temporal().latest().is_some() results = search_edges(g, filter_expr) assert [ ("N1", "N2"), @@ -1539,7 +1539,7 @@ def test_search_edges_for_property_temporal_latest_is_none(): g = Graph() g = init_edges_graph(g) - filter_expr = filter.Edge.property("p1").temporal().latest().is_none() + filter_expr = filter.Property("p1").temporal().latest().is_none() results = search_edges(g, filter_expr) assert [] == results @@ -1548,8 +1548,8 @@ def test_search_edges_for_composite_filter(): g = Graph() g = init_edges_graph(g) - filter1 = filter.Edge.src() == "N13" - filter2 = filter.Edge.property("p1").temporal().latest() == 3 + filter1 = filter.Edge.src().name() == "N13" + filter2 = filter.Property("p1").temporal().latest() == 3 results = search_edges(g, filter1 & filter2) assert [("N13", "N14")] == results @@ -1558,7 +1558,7 @@ def test_search_edges_for_composite_filter_pg(): g = PersistentGraph() g = init_edges_graph(g) - filter1 = filter.Edge.src() == "N13" - filter2 = filter.Edge.property("p1").temporal().latest() == 3 + filter1 = filter.Edge.src().name() == "N13" + filter2 = filter.Property("p1").temporal().latest() == 3 results = search_edges(g, filter1 & filter2) assert [("N13", "N14")] == results diff --git a/raphtory-benchmark/benches/search_bench.rs b/raphtory-benchmark/benches/search_bench.rs index aefc1a054f..4fc702c4f9 100644 --- a/raphtory-benchmark/benches/search_bench.rs +++ b/raphtory-benchmark/benches/search_bench.rs @@ -11,24 +11,23 @@ use raphtory::{ properties::internal::{ ConstPropertiesOps, TemporalPropertiesOps, TemporalPropertyViewOps, }, - view::{ - internal::{CoreGraphOps, InternalStorageOps}, - SearchableGraphOps, - }, + view::{internal::CoreGraphOps, SearchableGraphOps}, }, graph::{ edge::EdgeView, node::NodeView, - views::property_filter::{ - resolve_as_property_filter, EdgeFilter, EdgeFilterOps, FilterExpr, FilterOperator, - FilterOperator::*, NodeFilter, NodeFilterOps, PropertyFilterOps, + views::filter::model::{ + filter_operator::{FilterOperator, FilterOperator::*}, + ComposableFilter, EdgeFilter, EdgeFilterOps, NodeFilter, NodeFilterBuilderOps, + PropertyFilterOps, }, }, }, prelude::{ EdgePropertyFilterOps, EdgeViewOps, Graph, GraphViewOps, NodePropertyFilterOps, - NodeViewOps, PropUnwrap, PropertyFilter, StableDecode, + NodeViewOps, PropUnwrap, PropertyFilter, }, + serialise::StableDecode, }; use raphtory_api::core::PropType; use rayon::prelude::*; @@ -43,7 +42,7 @@ static GRAPH: Lazy> = Lazy::new(|| { println!("Edges count = {}", graph.count_edges()); let start = Instant::now(); - let _ = graph.create_index().unwrap(); + let _ = graph.create_index_in_ram().unwrap(); let duration = start.elapsed(); println!("Time taken to initialize graph and indexes: {:?}", duration); @@ -202,7 +201,7 @@ fn convert_to_property_filter( prop_value: Prop, filter_op: FilterOperator, sampled_values: Option>, -) -> Option { +) -> Option { let mut rng = thread_rng(); match prop_value.dtype() { @@ -220,9 +219,9 @@ fn convert_to_property_filter( Eq => Some(PropertyFilter::property(prop_name).eq(sub_str)), Ne => Some(PropertyFilter::property(prop_name).ne(sub_str)), In => sampled_values - .map(|vals| PropertyFilter::property(prop_name).includes(vals)), + .map(|vals| PropertyFilter::property(prop_name).is_in(vals)), NotIn => sampled_values - .map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + .map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => None, // No numeric comparison for strings } } else { @@ -230,9 +229,9 @@ fn convert_to_property_filter( Eq => Some(PropertyFilter::property(prop_name).eq(full_str)), Ne => Some(PropertyFilter::property(prop_name).ne(full_str)), In => sampled_values - .map(|vals| PropertyFilter::property(prop_name).includes(vals)), + .map(|vals| PropertyFilter::property(prop_name).is_in(vals)), NotIn => sampled_values - .map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + .map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => None, // No numeric comparison for strings } } @@ -249,8 +248,8 @@ fn convert_to_property_filter( Le => Some(PropertyFilter::property(prop_name).le(v)), Gt => Some(PropertyFilter::property(prop_name).gt(v)), Ge => Some(PropertyFilter::property(prop_name).ge(v)), - In => sampled_values.map(|vals| PropertyFilter::property(prop_name).includes(vals)), - NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + In => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_in(vals)), + NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::I64 => prop_value.into_i64().and_then(|v| match filter_op { @@ -260,8 +259,8 @@ fn convert_to_property_filter( Le => Some(PropertyFilter::property(prop_name).le(v)), Gt => Some(PropertyFilter::property(prop_name).gt(v)), Ge => Some(PropertyFilter::property(prop_name).ge(v)), - In => sampled_values.map(|vals| PropertyFilter::property(prop_name).includes(vals)), - NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + In => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_in(vals)), + NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::F64 => prop_value.into_f64().and_then(|v| match filter_op { @@ -271,15 +270,15 @@ fn convert_to_property_filter( Le => Some(PropertyFilter::property(prop_name).le(v)), Gt => Some(PropertyFilter::property(prop_name).gt(v)), Ge => Some(PropertyFilter::property(prop_name).ge(v)), - In => sampled_values.map(|vals| PropertyFilter::property(prop_name).includes(vals)), - NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + In => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_in(vals)), + NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => return None, }), PropType::Bool => prop_value.into_bool().and_then(|v| match filter_op { Eq => Some(PropertyFilter::property(prop_name).eq(v)), Ne => Some(PropertyFilter::property(prop_name).ne(v)), - In => sampled_values.map(|vals| PropertyFilter::property(prop_name).includes(vals)), - NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).excludes(vals)), + In => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_in(vals)), + NotIn => sampled_values.map(|vals| PropertyFilter::property(prop_name).is_not_in(vals)), _ => return None, }), @@ -321,7 +320,7 @@ fn pick_node_property_filter( props: &[(String, usize)], is_const: bool, filter_op: FilterOperator, -) -> Option { +) -> Option { let mut rng = thread_rng(); if let Some((prop_name, prop_id)) = props.choose(&mut rng) { let prop_value = if is_const { @@ -341,7 +340,10 @@ fn pick_node_property_filter( } } -fn get_random_node_property_filters(graph: &Graph, filter_op: FilterOperator) -> Vec { +fn get_random_node_property_filters( + graph: &Graph, + filter_op: FilterOperator, +) -> Vec { let mut rng = thread_rng(); let node_names = get_random_node_names(graph); @@ -428,7 +430,7 @@ fn pick_edge_property_filter( props: &[(String, usize)], is_const: bool, filter_op: FilterOperator, -) -> Option { +) -> Option { let mut rng = thread_rng(); if let Some((prop_name, prop_id)) = props.choose(&mut rng) { @@ -449,7 +451,10 @@ fn pick_edge_property_filter( } } -fn get_random_edge_property_filters(graph: &Graph, filter_op: FilterOperator) -> Vec { +fn get_random_edge_property_filters( + graph: &Graph, + filter_op: FilterOperator, +) -> Vec { let mut rng = thread_rng(); let edges = get_random_edges_by_src_dst_names(graph); @@ -533,9 +538,8 @@ fn bench_search_nodes_by_property_filter( b.iter_batched( || iter.next().unwrap(), |random_filter| { - let prop_filter = resolve_as_property_filter(random_filter).unwrap(); graph - .filter_nodes(prop_filter) + .filter_nodes(random_filter) .unwrap() .nodes() .into_iter() @@ -604,9 +608,8 @@ fn bench_search_edges_by_property_filter( b.iter_batched( || iter.next().unwrap().clone(), |random_filter| { - let prop_filter = resolve_as_property_filter(random_filter).unwrap(); graph - .filter_edges(prop_filter) + .filter_edges(random_filter) .unwrap() .edges() .into_iter() @@ -657,7 +660,7 @@ fn bench_search_nodes_by_name(c: &mut Criterion) { || { let mut iter = node_names.iter().cloned().cycle(); let random_name = iter.next().unwrap(); - NodeFilter::node_name().eq(random_name) + NodeFilter::name().eq(random_name) }, |random_filter| { graph.search_nodes(random_filter, 5, 0).unwrap(); @@ -779,8 +782,9 @@ fn bench_search_edges_by_src_dst(c: &mut Criterion) { let random_src_name = random_name.0; let random_dst_name = random_name.1; EdgeFilter::src() + .name() .eq(random_src_name) - .and(EdgeFilter::dst().eq(random_dst_name)) + .and(EdgeFilter::dst().name().eq(random_dst_name)) }, |random_filter| { graph.search_edges(random_filter, 5, 0).unwrap(); diff --git a/raphtory-graphql/src/model/graph/filtering.rs b/raphtory-graphql/src/model/graph/filtering.rs index 36de52d2fa..33e471a168 100644 --- a/raphtory-graphql/src/model/graph/filtering.rs +++ b/raphtory-graphql/src/model/graph/filtering.rs @@ -1,15 +1,30 @@ use crate::model::graph::property::Value; -use dynamic_graphql::{Enum, InputObject}; +use async_graphql::dynamic::{TypeRef, ValueAccessor}; +use dynamic_graphql::{ + internal::{ + FromValue, GetInputTypeRef, InputTypeName, InputValueError, InputValueResult, Register, + Registry, TypeName, TypeRefBuilder, + }, + Enum, InputObject, +}; +use futures_util::TryFutureExt; +use itertools::Itertools; use raphtory::{ core::{utils::errors::GraphError, Prop}, - db::graph::views::property_filter::{ - Filter, FilterExpr, FilterOperator, FilterValue, PropertyRef, Temporal, + db::graph::views::filter::model::{ + edge_filter::CompositeEdgeFilter, + filter_operator::FilterOperator, + node_filter::CompositeNodeFilter, + property_filter::{PropertyFilter, PropertyFilterValue, PropertyRef, Temporal}, + Filter, FilterValue, }, - prelude::PropertyFilter, }; use std::{ + borrow::Cow, fmt, fmt::{Display, Formatter}, + ops::Deref, + sync::Arc, }; #[derive(InputObject, Clone, Debug)] @@ -34,8 +49,8 @@ pub struct GraphViewCollection { pub shrink_window: Option, pub shrink_start: Option, pub shrink_end: Option, - pub node_filter: Option, - pub edge_filter: Option, + pub node_filter: Option, + pub edge_filter: Option, } #[derive(InputObject, Clone, Debug)] @@ -56,7 +71,7 @@ pub struct NodesViewCollection { pub shrink_start: Option, pub shrink_end: Option, pub type_filter: Option>, - pub node_filter: Option, + pub node_filter: Option, } #[derive(InputObject, Clone, Debug)] @@ -127,6 +142,7 @@ pub struct FilterProperty { pub property: String, pub condition: FilterCondition, } + #[derive(InputObject, Clone, Debug)] pub struct FilterCondition { pub operator: Operator, @@ -143,8 +159,10 @@ pub enum Operator { LessThan, IsNone, IsSome, - Any, - NotAny, + IsIn, + IsNotIn, + Contains, + NotContains, } #[derive(InputObject, Clone, Debug)] @@ -155,6 +173,63 @@ pub struct NodeFilter { pub temporal_property: Option, pub and: Option>, pub or: Option>, + pub not: Option>, +} + +#[derive(Clone, Debug)] +pub struct Wrapped(Box); + +impl Deref for Wrapped { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl Register for Wrapped { + fn register(registry: Registry) -> Registry { + registry.register::() + } +} + +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()), + } + } +} + +impl TypeName for Wrapped { + fn get_type_name() -> Cow<'static, str> { + T::get_type_name() + } +} + +impl InputTypeName for Wrapped {} + +impl NodeFilter { + pub fn validate(&self) -> Result<(), GraphError> { + let fields_set = [ + self.node.is_some(), + self.property.is_some(), + self.constant_property.is_some(), + self.temporal_property.is_some(), + self.and.is_some(), + self.or.is_some(), + self.not.is_some(), + ]; + + let count = fields_set.iter().filter(|x| **x).count(); + + match count { + 0 => Err(GraphError::InvalidGqlFilter("At least one field in NodeFilter must be provided.".to_string())), + 1 => Ok(()), + _ => Err(GraphError::InvalidGqlFilter("Only one of node, property, constant_property, temporal_property, and/or must be provided.".to_string())), + } + } } #[derive(InputObject, Clone, Debug)] @@ -179,6 +254,30 @@ pub struct EdgeFilter { pub temporal_property: Option, pub and: Option>, pub or: Option>, + pub not: Option>, +} + +impl EdgeFilter { + pub fn validate(&self) -> Result<(), GraphError> { + let fields_set = [ + self.src.is_some(), + self.dst.is_some(), + self.property.is_some(), + self.constant_property.is_some(), + self.temporal_property.is_some(), + self.and.is_some(), + self.or.is_some(), + self.not.is_some(), + ]; + + let count = fields_set.iter().filter(|x| **x).count(); + + match count { + 0 => Err(GraphError::InvalidGqlFilter("At least one field in EdgeFilter must be provided.".to_string())), + 1 => Ok(()), + _ => Err(GraphError::InvalidGqlFilter("Only one of src, dst, property, constant_property, temporal_property, and/or must be provided.".to_string())), + } + } } #[derive(InputObject, Clone, Debug)] @@ -209,139 +308,214 @@ pub enum TemporalType { Latest, } -impl TryFrom for FilterExpr { +impl TryFrom for CompositeNodeFilter { type Error = GraphError; - fn try_from(node_filter: NodeFilter) -> Result { - let NodeFilter { - node, - property, - constant_property, - temporal_property, - and, - or, - } = node_filter; + fn try_from(filter: NodeFilter) -> Result { let mut exprs = Vec::new(); - if let Some(node) = node { - exprs.push(FilterExpr::Node(Filter { + if let Some(node) = filter.node { + exprs.push(CompositeNodeFilter::Node(Filter { field_name: node.field.to_string(), field_value: FilterValue::Single(node.value), operator: node.operator.into(), })); } - if let Some(property) = property { - exprs.push(FilterExpr::Property(property.try_into()?)); + if let Some(prop) = filter.property { + exprs.push(CompositeNodeFilter::Property(prop.try_into()?)); } - if let Some(constant_property) = constant_property { - exprs.push(FilterExpr::Property(constant_property.try_into()?)); + if let Some(constant_prop) = filter.constant_property { + exprs.push(CompositeNodeFilter::Property(constant_prop.try_into()?)); } - if let Some(temporal_property) = temporal_property { - exprs.push(FilterExpr::Property(temporal_property.try_into()?)); + if let Some(temporal_prop) = filter.temporal_property { + exprs.push(CompositeNodeFilter::Property(temporal_prop.try_into()?)); } - if let Some(and) = and { - exprs.push(FilterExpr::And( - and.into_iter() - .map(FilterExpr::try_from) - .collect::, _>>()?, - )); + if let Some(and_filters) = filter.and { + 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)) + }); + exprs.push(and_chain); + } } - if let Some(or) = or { - exprs.push(FilterExpr::Or( - or.into_iter() - .map(FilterExpr::try_from) - .collect::, _>>()?, - )); + if let Some(or_filters) = filter.or { + 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)) + }); + exprs.push(or_chain); + } } - Ok(match exprs.len() { - 1 => exprs.into_iter().next().unwrap(), - _ => FilterExpr::And(exprs), - }) + if let Some(not_filters) = filter.not { + let inner = CompositeNodeFilter::try_from(not_filters.deref().clone())?; + exprs.push(CompositeNodeFilter::Not(Box::new(inner))); + } + + let result = match exprs.len() { + 0 => Err(GraphError::ParsingError), + 1 => Ok(exprs.remove(0)), + _ => { + let mut iter = exprs.into_iter(); + let first = iter.next().unwrap(); + let and_chain = iter.fold(first, |acc, next| { + CompositeNodeFilter::And(Box::new(acc), Box::new(next)) + }); + Ok(and_chain) + } + }; + + result } } -impl TryFrom for FilterExpr { +impl TryFrom for CompositeEdgeFilter { type Error = GraphError; - fn try_from(edge_filter: EdgeFilter) -> Result { - let EdgeFilter { - src, - dst, - property, - constant_property, - temporal_property, - and, - or, - } = edge_filter; + fn try_from(filter: EdgeFilter) -> Result { let mut exprs = Vec::new(); - if let Some(src) = src { - exprs.push(FilterExpr::Edge(Filter { - field_name: "from".to_string(), + if let Some(src) = filter.src { + exprs.push(CompositeEdgeFilter::Edge(Filter { + field_name: "src".to_string(), field_value: FilterValue::Single(src.value), operator: src.operator.into(), })); } - if let Some(dst) = dst { - exprs.push(FilterExpr::Edge(Filter { - field_name: "to".to_string(), + if let Some(dst) = filter.dst { + exprs.push(CompositeEdgeFilter::Edge(Filter { + field_name: "dst".to_string(), field_value: FilterValue::Single(dst.value), operator: dst.operator.into(), })); } - if let Some(property) = property { - exprs.push(FilterExpr::Property(property.try_into()?)); + if let Some(prop) = filter.property { + exprs.push(CompositeEdgeFilter::Property(prop.try_into()?)); + } + + if let Some(prop) = filter.constant_property { + exprs.push(CompositeEdgeFilter::Property(prop.try_into()?)); + } + + if let Some(prop) = filter.temporal_property { + exprs.push(CompositeEdgeFilter::Property(prop.try_into()?)); } - if let Some(constant_property) = constant_property { - exprs.push(FilterExpr::Property(constant_property.try_into()?)); + if let Some(and_filters) = filter.and { + 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)) + }); + exprs.push(and_chain); + } } - if let Some(temporal_property) = temporal_property { - exprs.push(FilterExpr::Property(temporal_property.try_into()?)); + if let Some(or_filters) = filter.or { + 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)) + }); + exprs.push(or_chain); + } } - if let Some(and) = and { - exprs.push(FilterExpr::And( - and.into_iter() - .map(FilterExpr::try_from) - .collect::, _>>()?, - )); + if let Some(not_filters) = filter.not { + let inner = CompositeEdgeFilter::try_from(not_filters.deref().clone())?; + exprs.push(CompositeEdgeFilter::Not(Box::new(inner))); } - if let Some(or) = or { - exprs.push(FilterExpr::Or( - or.into_iter() - .map(FilterExpr::try_from) - .collect::, _>>()?, - )); + match exprs.len() { + 0 => Err(GraphError::ParsingError), + 1 => Ok(exprs.remove(0)), + _ => { + let mut iter = exprs.into_iter(); + let first = iter.next().unwrap(); + let and_chain = iter.fold(first, |acc, next| { + CompositeEdgeFilter::And(Box::new(acc), Box::new(next)) + }); + Ok(and_chain) + } } + } +} - Ok(match exprs.len() { - 1 => exprs.into_iter().next().unwrap(), - _ => FilterExpr::And(exprs), - }) +fn build_property_filter( + prop_ref: PropertyRef, + operator: Operator, + value: Option, +) -> Result { + let prop = value.map(Prop::try_from).transpose()?; + + // Validates required value + if matches!( + operator, + Operator::Equal + | Operator::NotEqual + | Operator::GreaterThan + | Operator::LessThan + | Operator::GreaterThanOrEqual + | Operator::LessThanOrEqual + | Operator::IsIn + | Operator::IsNotIn + | Operator::Contains + | Operator::NotContains + ) && prop.is_none() + { + return Err(GraphError::ExpectedValueForOperator( + "value".into(), + format!("{:?}", operator), + )); } + + 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(), + }) } impl TryFrom for PropertyFilter { type Error = GraphError; fn try_from(expr: PropertyFilterExpr) -> Result { - let prop = expr.value.map(Prop::try_from).transpose()?; - Ok(PropertyFilter { - prop_ref: PropertyRef::Property(expr.name), - prop_value: prop.into(), - operator: expr.operator.into(), - }) + build_property_filter(PropertyRef::Property(expr.name), expr.operator, expr.value) } } @@ -349,24 +523,23 @@ impl TryFrom for PropertyFilter { type Error = GraphError; fn try_from(expr: ConstantPropertyFilterExpr) -> Result { - let prop = expr.value.map(Prop::try_from).transpose()?; - Ok(PropertyFilter { - prop_ref: PropertyRef::ConstantProperty(expr.name), - prop_value: prop.into(), - operator: expr.operator.into(), - }) + build_property_filter( + PropertyRef::ConstantProperty(expr.name), + expr.operator, + expr.value, + ) } } impl TryFrom for PropertyFilter { type Error = GraphError; + fn try_from(expr: TemporalPropertyFilterExpr) -> Result { - let prop = expr.value.map(Prop::try_from).transpose()?; - Ok(PropertyFilter { - prop_ref: PropertyRef::TemporalProperty(expr.name, expr.temporal.into()), - prop_value: prop.into(), - operator: expr.operator.into(), - }) + build_property_filter( + PropertyRef::TemporalProperty(expr.name, expr.temporal.into()), + expr.operator, + expr.value, + ) } } @@ -389,10 +562,12 @@ impl From for FilterOperator { Operator::LessThanOrEqual => FilterOperator::Le, Operator::GreaterThan => FilterOperator::Gt, Operator::LessThan => FilterOperator::Lt, - Operator::Any => FilterOperator::In, - Operator::NotAny => FilterOperator::NotIn, + Operator::IsIn => FilterOperator::In, + Operator::IsNotIn => FilterOperator::NotIn, Operator::IsSome => FilterOperator::IsSome, Operator::IsNone => FilterOperator::IsNone, + Operator::Contains => FilterOperator::Contains, + Operator::NotContains => FilterOperator::NotContains, } } } diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index 910d91851c..9593e19521 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -4,7 +4,7 @@ use crate::{ graph::{ edge::Edge, edges::GqlEdges, - filtering::{EdgeFilter, FilterCondition, GraphViewCollection, NodeFilter, Operator}, + filtering::{EdgeFilter, GraphViewCollection, NodeFilter}, node::Node, nodes::GqlNodes, property::GqlProperties, @@ -26,15 +26,24 @@ use raphtory::{ api::{ properties::dyn_props::DynProperties, view::{ - DynamicGraph, IntoDynamic, NodeViewOps, SearchableGraphOps, StaticGraphViewOps, - TimeOps, + internal::DelegateCoreOps, DynamicGraph, IntoDynamic, NodeViewOps, + SearchableGraphOps, StaticGraphViewOps, TimeOps, + }, + }, + graph::{ + node::NodeView, + views::filter::model::{ + edge_filter::CompositeEdgeFilter, node_filter::CompositeNodeFilter, }, }, - graph::{node::NodeView, views::property_filter::PropertyRef}, }, prelude::*, }; -use std::{collections::HashSet, convert::Into, sync::Arc}; +use std::{ + collections::HashSet, + convert::{Into, TryInto}, + sync::Arc, +}; #[derive(ResolvedObject)] pub(crate) struct GqlGraph { @@ -394,324 +403,24 @@ impl GqlGraph { Ok(true) } - async fn node_filter( - &self, - property: String, - condition: FilterCondition, - ) -> Result { - let prop_ref = PropertyRef::Property(property); - match condition.operator { - Operator::Equal => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::eq(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "Equal".into(), - )) - } - } - Operator::NotEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::ne(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "NotEqual".into(), - )) - } - } - Operator::GreaterThanOrEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::ge(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThanOrEqual".into(), - )) - } - } - Operator::LessThanOrEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::le(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThanOrEqual".into(), - )) - } - } - Operator::GreaterThan => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::gt(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThan".into(), - )) - } - } - Operator::LessThan => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::lt(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThan".into(), - )) - } - } - Operator::IsNone => { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::is_none(prop_ref.clone()))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } - Operator::IsSome => { - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::is_some(prop_ref.clone()))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } - Operator::Any => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::includes(prop_ref.clone(), prop_values))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "Any".into(), - )) - } - } - Operator::NotAny => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_graph = self - .graph - .filter_nodes(PropertyFilter::excludes(prop_ref.clone(), prop_values))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "NotAny".into(), - )) - } - } - } + async fn node_filter(&self, filter: NodeFilter) -> Result { + filter.validate()?; + let filter: CompositeNodeFilter = filter.try_into()?; + let filtered_graph = self.graph.filter_nodes(filter)?; + Ok(GqlGraph::new( + self.path.clone(), + filtered_graph.into_dynamic(), + )) } - async fn edge_filter( - &self, - property: String, - condition: FilterCondition, - ) -> Result { - let prop_ref = PropertyRef::Property(property); - match condition.operator { - Operator::Equal => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::eq(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "Equal".into(), - )) - } - } - Operator::NotEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::ne(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "NotEqual".into(), - )) - } - } - Operator::GreaterThanOrEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::ge(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThanOrEqual".into(), - )) - } - } - Operator::LessThanOrEqual => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::le(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThanOrEqual".into(), - )) - } - } - Operator::GreaterThan => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::gt(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThan".into(), - )) - } - } - Operator::LessThan => { - if let Some(v) = condition.value { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::lt(prop_ref.clone(), Prop::try_from(v)?))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThan".into(), - )) - } - } - Operator::IsNone => { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::is_none(prop_ref.clone()))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } - Operator::IsSome => { - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::is_some(prop_ref.clone()))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } - Operator::Any => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::includes(prop_ref.clone(), prop_values))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "Any".into(), - )) - } - } - Operator::NotAny => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_graph = self - .graph - .filter_edges(PropertyFilter::excludes(prop_ref.clone(), prop_values))?; - Ok(GqlGraph::new( - self.path.clone(), - filtered_graph.into_dynamic(), - )) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "NotAny".into(), - )) - } - } - } + async fn edge_filter(&self, filter: EdgeFilter) -> Result { + filter.validate()?; + let filter: CompositeEdgeFilter = filter.try_into()?; + let filtered_graph = self.graph.filter_edges(filter)?; + Ok(GqlGraph::new( + self.path.clone(), + filtered_graph.into_dynamic(), + )) } //////////////////////// @@ -723,10 +432,12 @@ impl GqlGraph { limit: usize, offset: usize, ) -> Result, GraphError> { + filter.validate()?; + let f: CompositeNodeFilter = filter.try_into()?; self.execute_search(|| { Ok(self .graph - .search_nodes(filter.try_into()?, limit, offset) + .search_nodes(f, limit, offset) .into_iter() .flatten() .map(|vv| vv.into()) @@ -741,10 +452,12 @@ impl GqlGraph { limit: usize, offset: usize, ) -> Result, GraphError> { + filter.validate()?; + let f: CompositeEdgeFilter = filter.try_into()?; self.execute_search(|| { Ok(self .graph - .search_edges(filter.try_into()?, limit, offset) + .search_edges(f, limit, offset) .into_iter() .flatten() .map(|vv| vv.into()) @@ -840,15 +553,11 @@ impl GqlGraph { } if let Some(node_filter) = view.node_filter { count += 1; - return_view = return_view - .node_filter(node_filter.property, node_filter.condition) - .await?; + return_view = return_view.node_filter(node_filter).await?; } if let Some(edge_filter) = view.edge_filter { count += 1; - return_view = return_view - .edge_filter(edge_filter.property, edge_filter.condition) - .await?; + return_view = return_view.edge_filter(edge_filter).await?; } if count > 1 { diff --git a/raphtory-graphql/src/model/graph/nodes.rs b/raphtory-graphql/src/model/graph/nodes.rs index 93f0d28e36..59081e0170 100644 --- a/raphtory-graphql/src/model/graph/nodes.rs +++ b/raphtory-graphql/src/model/graph/nodes.rs @@ -1,6 +1,6 @@ use crate::model::{ graph::{ - filtering::{FilterCondition, NodesViewCollection, Operator}, + filtering::{NodeFilter, NodesViewCollection}, node::Node, }, sorting::{NodeSortBy, SortByTime}, @@ -11,7 +11,7 @@ use raphtory::{ core::utils::errors::GraphError, db::{ api::{state::Index, view::DynamicGraph}, - graph::{nodes::Nodes, views::property_filter::PropertyRef}, + graph::{nodes::Nodes, views::filter::model::node_filter::CompositeNodeFilter}, }, prelude::*, }; @@ -109,141 +109,11 @@ impl GqlNodes { self.update(self.nn.type_filter(&node_types)) } - async fn node_filter( - &self, - property: String, - condition: FilterCondition, - ) -> Result { - match condition.operator { - Operator::Equal => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::eq( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "Equal".into(), - )) - } - } - Operator::NotEqual => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::ne( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "NotEqual".into(), - )) - } - } - Operator::GreaterThanOrEqual => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::ge( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThanOrEqual".into(), - )) - } - } - Operator::LessThanOrEqual => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::le( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThanOrEqual".into(), - )) - } - } - Operator::GreaterThan => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::gt( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "GreaterThan".into(), - )) - } - } - Operator::LessThan => { - if let Some(v) = condition.value { - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::lt( - PropertyRef::Property(property), - Prop::try_from(v)?, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "value".into(), - "LessThan".into(), - )) - } - } - Operator::IsNone => { - let filtered_nodes = self - .nn - .filter_nodes(PropertyFilter::is_none(PropertyRef::Property(property)))?; - Ok(self.update(filtered_nodes)) - } - Operator::IsSome => { - let filtered_nodes = self - .nn - .filter_nodes(PropertyFilter::is_some(PropertyRef::Property(property)))?; - Ok(self.update(filtered_nodes)) - } - Operator::Any => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::includes( - PropertyRef::Property(property), - prop_values, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "Any".into(), - )) - } - } - Operator::NotAny => { - if let Some(Prop::List(list)) = condition.value.and_then(|v| Prop::try_from(v).ok()) - { - let prop_values: Vec = list.iter().cloned().collect(); - let filtered_nodes = self.nn.filter_nodes(PropertyFilter::excludes( - PropertyRef::Property(property), - prop_values, - ))?; - Ok(self.update(filtered_nodes)) - } else { - Err(GraphError::ExpectedValueForOperator( - "list".into(), - "NotAny".into(), - )) - } - } - } + async fn node_filter(&self, filter: NodeFilter) -> Result { + filter.validate()?; + let filter: CompositeNodeFilter = filter.try_into()?; + let filtered_nodes = self.nn.filter_nodes(filter)?; + Ok(self.update(filtered_nodes.into_dyn())) } async fn apply_views(&self, views: Vec) -> Result { @@ -317,9 +187,7 @@ impl GqlNodes { } if let Some(node_filter) = view.node_filter { count += 1; - return_view = return_view - .node_filter(node_filter.property, node_filter.condition) - .await?; + return_view = return_view.node_filter(node_filter).await?; } if count > 1 { diff --git a/raphtory-graphql/src/model/mod.rs b/raphtory-graphql/src/model/mod.rs index 45555565d4..fd79de8f79 100644 --- a/raphtory-graphql/src/model/mod.rs +++ b/raphtory-graphql/src/model/mod.rs @@ -23,6 +23,7 @@ use raphtory::{ core::utils::errors::{GraphError, InvalidPathReason}, db::{api::view::MaterializedGraph, graph::views::deletion_graph::PersistentGraph}, prelude::*, + serialise::InternalStableDecode, }; use std::{ error::Error, diff --git a/raphtory-graphql/src/model/schema/node_schema.rs b/raphtory-graphql/src/model/schema/node_schema.rs index 7407e16c80..acfeb1dc4f 100644 --- a/raphtory-graphql/src/model/schema/node_schema.rs +++ b/raphtory-graphql/src/model/schema/node_schema.rs @@ -3,7 +3,7 @@ use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use raphtory::{ db::{ api::view::{internal::CoreGraphOps, DynamicGraph}, - graph::views::node_type_filtered_subgraph::TypeFilteredSubgraph, + graph::views::filter::node_type_filtered_graph::NodeTypeFilteredGraph, }, prelude::{GraphViewOps, NodeStateOps, NodeViewOps}, }; @@ -95,8 +95,11 @@ impl NodeSchema { keys.into_par_iter() .zip(property_types) .filter_map(|(key, dtype)| { + let mut node_types_filter = + vec![false; self.graph.node_meta().node_type_meta().len()]; + node_types_filter[self.type_id] = true; let unique_values: ahash::HashSet<_> = - TypeFilteredSubgraph::new(self.graph.clone(), vec![self.type_id]) + NodeTypeFilteredGraph::new(self.graph.clone(), node_types_filter.into()) .nodes() .properties() .into_iter_values() diff --git a/raphtory-graphql/src/url_encode.rs b/raphtory-graphql/src/url_encode.rs index 95a43fd02f..9db6d4cdab 100644 --- a/raphtory-graphql/src/url_encode.rs +++ b/raphtory-graphql/src/url_encode.rs @@ -2,7 +2,7 @@ use base64::{prelude::BASE64_URL_SAFE, DecodeError, Engine}; use raphtory::{ core::utils::errors::GraphError, db::api::view::MaterializedGraph, - serialise::{StableDecode, StableEncode}, + serialise::{InternalStableDecode, StableEncode}, }; #[derive(thiserror::Error, Debug)] diff --git a/raphtory/Cargo.toml b/raphtory/Cargo.toml index 21231c1823..8a31708615 100644 --- a/raphtory/Cargo.toml +++ b/raphtory/Cargo.toml @@ -91,6 +91,8 @@ prost-types = { workspace = true, optional = true } roaring = { workspace = true } fake = { workspace = true } strsim = { workspace = true } +walkdir = { workspace = true } +uuid = { workspace = true } [dev-dependencies] csv = { workspace = true } @@ -113,7 +115,6 @@ prost-build = { workspace = true, optional = true } default = [] # Enables the graph loader io module io = [ - "dep:zip", "dep:neo4rs", "dep:bzip2", "dep:flate2", @@ -126,7 +127,7 @@ io = [ ] # search -search = ["dep:tantivy", "proto"] +search = ["dep:tantivy", "dep:tempfile", "proto", "io"] # vectors vectors = [ "dep:futures-util", @@ -172,9 +173,9 @@ arrow = [ proto = [ "dep:prost", "dep:prost-types", + "dep:zip", "dep:prost-build", "dep:memmap2", - "dep:zip", "arrow", "io", ] diff --git a/raphtory/src/core/utils/errors.rs b/raphtory/src/core/utils/errors.rs index 34e2886064..6eea199e59 100644 --- a/raphtory/src/core/utils/errors.rs +++ b/raphtory/src/core/utils/errors.rs @@ -1,6 +1,6 @@ use crate::{ core::{storage::lazy_vec::IllegalSet, utils::time::error::ParseTimeError, Prop}, - db::graph::views::property_filter::{FilterExpr, FilterOperator}, + db::graph::views::filter::model::filter_operator::FilterOperator, }; #[cfg(feature = "io")] use parquet::errors::ParquetError; @@ -135,6 +135,14 @@ pub enum GraphError { IndexNotCreated, #[error("Failed to create index.")] FailedToCreateIndex, + #[error("Failed to persist index.")] + FailedToPersistIndex, + #[error("Graph index is missing")] + GraphIndexIsMissing, + #[error("Failed to remove existing graph index: {0}")] + FailedToRemoveExistingGraphIndex(PathBuf), + #[error("Failed to move graph index")] + FailedToMoveGraphIndex, #[error("Disk Graph is immutable")] ImmutableDiskGraph, #[error("Event Graph doesn't support deletions")] @@ -216,6 +224,9 @@ pub enum GraphError { source: io::Error, }, + #[error("IO operation failed: {0}")] + IOErrorMsg(String), + #[cfg(feature = "proto")] #[error("zip operation failed")] ZipError { @@ -245,12 +256,16 @@ pub enum GraphError { DiskGraphError(#[from] RAError), #[cfg(feature = "search")] - #[error("Index operation failed")] + #[error("Index operation failed: {source}")] IndexError { #[from] source: tantivy::TantivyError, }, + #[cfg(feature = "search")] + #[error("Index operation failed: {0}")] + IndexErrorMsg(String), + #[cfg(feature = "vectors")] #[error("Embedding operation failed")] EmbeddingError { @@ -335,6 +350,9 @@ pub enum GraphError { #[error("Operator {0} requires a property value, but none was provided.")] InvalidFilter(FilterOperator), + #[error("Invalid filter: {0}")] + InvalidGqlFilter(String), + #[error("Property {0} not found in temporal or constant metadata")] PropertyNotFound(String), @@ -356,11 +374,17 @@ pub enum GraphError { #[error("Unsupported Value: {0}")] UnsupportedValue(String), - #[error("Illegal FilterExpr: {0}, Reason: {1}.")] - IllegalFilterExpr(FilterExpr, String), - #[error("Value cannot be empty.")] EmptyValue, + + #[error("Filter must contain at least one filter condition.")] + ParsingError, + + #[error("Indexing not supported")] + IndexingNotSupported, + + #[error("Failed to create index in ram")] + FailedToCreateIndexInRam, } impl GraphError { diff --git a/raphtory/src/db/api/properties/internal.rs b/raphtory/src/db/api/properties/internal.rs index 1d8ade9d9f..0037872299 100644 --- a/raphtory/src/db/api/properties/internal.rs +++ b/raphtory/src/db/api/properties/internal.rs @@ -82,13 +82,13 @@ impl Pr pub trait InheritTemporalPropertyViewOps: Base {} pub trait InheritTemporalPropertiesOps: Base {} -pub trait InheritStaticPropertiesOps: Base + Send + Sync {} -pub trait InheritPropertiesOps: Base + Send + Sync {} +pub trait InheritStaticPropertiesOps: Base {} +pub trait InheritPropertiesOps: Base {} impl InheritStaticPropertiesOps for P {} impl InheritTemporalPropertiesOps for P {} -impl TemporalPropertyViewOps for P +impl TemporalPropertyViewOps for P where P::Base: TemporalPropertyViewOps, { @@ -123,7 +123,7 @@ where impl InheritTemporalPropertyViewOps for P {} -impl TemporalPropertiesOps for P +impl TemporalPropertiesOps for P where P::Base: TemporalPropertiesOps, { @@ -148,7 +148,7 @@ where } } -impl ConstPropertiesOps for P +impl ConstPropertiesOps for P where P::Base: ConstPropertiesOps, { diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index 9f5753ef20..e7f8cafcb4 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -276,14 +276,14 @@ impl<'graph, Op: NodeOp + 'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'gra self.iter().nth(index) } else { let vid = match self.graph().node_list() { - NodeList::All { num_nodes } => { - if index < num_nodes { + NodeList::All { len } => { + if index < len { VID(index) } else { return None; } } - NodeList::List { nodes } => nodes.key(index)?, + NodeList::List { elems } => elems.key(index)?, }; let cg = self.graph().core_graph(); Some(( diff --git a/raphtory/src/db/api/state/node_state.rs b/raphtory/src/db/api/state/node_state.rs index 4886d153e6..223935b806 100644 --- a/raphtory/src/db/api/state/node_state.rs +++ b/raphtory/src/db/api/state/node_state.rs @@ -20,11 +20,18 @@ use std::{ sync::Arc, }; -#[derive(Clone, Debug, Default)] +#[derive(Debug, Default)] pub struct Index { index: Arc>, } +impl Clone for Index { + fn clone(&self) -> Self { + let index = self.index.clone(); + Self { index } + } +} + impl + From + Send + Sync> FromIterator for Index { fn from_iter>(iter: T) -> Self { Self { @@ -39,7 +46,7 @@ impl Index { if graph.node_list_trusted() { match graph.node_list() { NodeList::All { .. } => None, - NodeList::List { nodes } => Some(nodes), + NodeList::List { elems } => Some(elems), } } else { Some(Self::from_iter(graph.nodes().iter().map(|node| node.node))) @@ -95,6 +102,10 @@ impl + From + Send + Sync> Index { .into_par_iter() .map(move |i| *self.index.get_index(i).unwrap()) } + + pub fn intersection(&self, other: &Self) -> Self { + self.index.intersection(&other.index).copied().collect() + } } #[derive(Clone)] diff --git a/raphtory/src/db/api/storage/graph/storage_ops/list_ops.rs b/raphtory/src/db/api/storage/graph/storage_ops/list_ops.rs index 31d3d2c768..93ec9be9fb 100644 --- a/raphtory/src/db/api/storage/graph/storage_ops/list_ops.rs +++ b/raphtory/src/db/api/storage/graph/storage_ops/list_ops.rs @@ -5,13 +5,13 @@ use super::GraphStorage; impl ListOps for GraphStorage { fn node_list(&self) -> NodeList { NodeList::All { - num_nodes: self.internal_num_nodes(), + len: self.internal_num_nodes(), } } fn edge_list(&self) -> EdgeList { EdgeList::All { - num_edges: self.internal_num_edges(), + len: self.internal_num_edges(), } } } diff --git a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs index bd5ee12c71..a3e81dec05 100644 --- a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs +++ b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs @@ -326,9 +326,9 @@ impl GraphStorage { .par_iter() .filter(|node| view.filter_node(*node, layer_ids)) .count(), - NodeList::List { nodes } => { + NodeList::List { elems } => { let nodes_storage = self.nodes(); - nodes + elems .par_iter() .filter(|&vid| view.filter_node(nodes_storage.node(vid), layer_ids)) .count() @@ -431,8 +431,8 @@ impl GraphStorage { view: G, ) -> impl Iterator + Send + 'graph { match view.node_list() { - NodeList::List { nodes } => { - return nodes + NodeList::List { elems } => { + return elems .into_iter() .flat_map(move |v| { self.clone() diff --git a/raphtory/src/db/api/storage/graph/storage_ops/time_semantics.rs b/raphtory/src/db/api/storage/graph/storage_ops/time_semantics.rs index 301cde9cb6..644d78ed8b 100644 --- a/raphtory/src/db/api/storage/graph/storage_ops/time_semantics.rs +++ b/raphtory/src/db/api/storage/graph/storage_ops/time_semantics.rs @@ -1250,7 +1250,7 @@ mod test_graph_storage { mod search_nodes { use super::*; use crate::{ - db::{api::view::SearchableGraphOps, graph::views::property_filter::PropertyFilterOps}, + db::{api::view::SearchableGraphOps, graph::views::filter::model::PropertyFilterOps}, prelude::{Graph, NodeViewOps, PropertyFilter}, }; @@ -1276,7 +1276,7 @@ mod test_graph_storage { mod search_edges { use super::*; use crate::{ - db::{api::view::SearchableGraphOps, graph::views::property_filter::PropertyFilterOps}, + db::{api::view::SearchableGraphOps, graph::views::filter::model::PropertyFilterOps}, prelude::{EdgeViewOps, Graph, NodeViewOps, PropertyFilter}, }; #[test] diff --git a/raphtory/src/db/api/storage/storage.rs b/raphtory/src/db/api/storage/storage.rs index 23454bd077..ccd508f9f7 100644 --- a/raphtory/src/db/api/storage/storage.rs +++ b/raphtory/src/db/api/storage/storage.rs @@ -44,8 +44,10 @@ use raphtory_api::core::{ use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Formatter}, + path::PathBuf, sync::Arc, }; +use tracing::info; #[derive(Debug, Default, Serialize, Deserialize)] pub struct Storage { @@ -74,6 +76,9 @@ impl Base for Storage { } } +#[cfg(feature = "search")] +const IN_MEMORY_INDEX_NOT_PERSISTED: &str = "In-memory index not persisted. Not supported"; + impl Storage { pub(crate) fn new(num_locks: usize) -> Self { Self { @@ -135,14 +140,60 @@ impl Storage { #[cfg(feature = "search")] impl Storage { - pub(crate) fn get_or_create_index(&self) -> Result<&GraphIndex, GraphError> { - self.index - .get_or_try_init(|| Ok::<_, GraphError>(GraphIndex::try_from(&self.graph)?)) + pub(crate) fn get_or_create_index( + &self, + path: Option, + ) -> Result<&GraphIndex, GraphError> { + self.index.get_or_try_init(|| { + if let Some(path) = path { + Ok::<_, GraphError>(GraphIndex::load_from_path(&path)?) + } else { + let cache_path = self.get_cache().map(|cache| cache.folder.get_base_path()); + Ok::<_, GraphError>(GraphIndex::create_from_graph( + &self.graph, + false, + cache_path, + )?) + } + }) + } + + pub(crate) fn get_or_create_index_in_ram(&self) -> Result<&GraphIndex, GraphError> { + let index = self.index.get_or_try_init(|| { + Ok::<_, GraphError>(GraphIndex::create_from_graph(&self.graph, true, None)?) + })?; + if index.path.is_some() { + Err(GraphError::FailedToCreateIndexInRam) + } else { + Ok(index) + } } pub(crate) fn get_index(&self) -> Option<&GraphIndex> { self.index.get() } + + pub(crate) fn persist_index_to_disk(&self, path: &PathBuf) -> Result<(), GraphError> { + if let Some(index) = self.get_index() { + if index.path.is_none() { + info!("{}", IN_MEMORY_INDEX_NOT_PERSISTED); + return Ok(()); + } + index.persist_to_disk(path)? + } + Ok(()) + } + + pub(crate) fn persist_index_to_disk_zip(&self, path: &PathBuf) -> Result<(), GraphError> { + if let Some(index) = self.get_index() { + if index.path.is_none() { + info!("{}", IN_MEMORY_INDEX_NOT_PERSISTED); + return Ok(()); + } + index.persist_to_disk_zip(path)? + } + Ok(()) + } } impl InternalStorageOps for Storage { @@ -207,7 +258,7 @@ impl InternalAdditionOps for Storage { fn resolve_node(&self, id: V) -> Result, GraphError> { match id.as_node_ref() { - NodeRef::Internal(id) => Ok(MaybeNew::Existing(id)), + NodeRef::Internal(id) => Ok(Existing(id)), NodeRef::External(gid) => { let id = self.graph.resolve_node(gid)?; diff --git a/raphtory/src/db/api/view/edge_property_filter.rs b/raphtory/src/db/api/view/edge_property_filter.rs index 5e7a59c6f1..e2ab014e3d 100644 --- a/raphtory/src/db/api/view/edge_property_filter.rs +++ b/raphtory/src/db/api/view/edge_property_filter.rs @@ -2,7 +2,7 @@ use crate::{ core::utils::errors::GraphError, db::{ api::view::internal::{InternalMaterialize, OneHopFilter}, - graph::views::property_filter::internal::InternalEdgeFilterOps, + graph::views::filter::internal::InternalEdgeFilterOps, }, prelude::GraphViewOps, }; @@ -28,13 +28,44 @@ impl<'graph, G: GraphViewOps<'graph>> EdgePropertyFilterOps<'graph> for G {} #[cfg(test)] mod test { use crate::{ - db::graph::views::property_filter::{PropertyFilter, PropertyRef}, + db::graph::views::filter::model::{ + property_filter::{PropertyFilter, PropertyRef}, + ComposableFilter, EdgeFilter, EdgeFilterOps, + }, prelude::*, test_utils::{build_edge_list, build_graph_from_edge_list}, }; use itertools::Itertools; use proptest::{arbitrary::any, proptest}; + #[test] + fn test_edge_filter_on_edges() { + use crate::db::graph::views::filter::model::PropertyFilterOps; + + let g = Graph::new(); + g.add_edge(0, "Jimi", "John", [("band", "JH Experience")], None) + .unwrap(); + g.add_edge(1, "John", "David", [("band", "Dead & Company")], None) + .unwrap(); + g.add_edge(2, "David", "Jimi", [("band", "Pink Floyd")], None) + .unwrap(); + + let filter_expr = EdgeFilter::dst() + .name() + .eq("David") + .and(PropertyFilter::property("band").eq("Dead & Company")); + let filtered_edges = g.filter_edges(filter_expr).unwrap(); + + assert_eq!( + filtered_edges + .edges() + .iter() + .map(|e| format!("{}->{}", e.src().name(), e.dst().name())) + .collect::>(), + vec!["John->David"] + ); + } + #[test] fn test_edge_property_filter_on_nodes() { let g = Graph::new(); diff --git a/raphtory/src/db/api/view/exploded_edge_property_filter.rs b/raphtory/src/db/api/view/exploded_edge_property_filter.rs index c148317212..98fcb24e23 100644 --- a/raphtory/src/db/api/view/exploded_edge_property_filter.rs +++ b/raphtory/src/db/api/view/exploded_edge_property_filter.rs @@ -1,326 +1,326 @@ -use crate::{ - core::utils::errors::GraphError, - db::{ - api::view::internal::{InternalMaterialize, OneHopFilter}, - graph::views::property_filter::internal::InternalExplodedEdgeFilterOps, - }, - prelude::GraphViewOps, -}; -use raphtory_api::GraphType; - -pub trait ExplodedEdgePropertyFilterOps<'graph>: OneHopFilter<'graph> { - fn filter_exploded_edges( - &self, - filter: F, - ) -> Result>, GraphError> - { - if matches!( - self.current_filter().graph_type(), - GraphType::PersistentGraph - ) { - return Err(GraphError::PropertyFilteringNotImplemented); - } - let graph = filter.create_exploded_edge_filter(self.current_filter().clone())?; - Ok(self.one_hop_filtered(graph)) - } -} - -impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilterOps<'graph> for G {} - -#[cfg(test)] -mod test { - use crate::{ - db::{ - api::view::exploded_edge_property_filter::ExplodedEdgePropertyFilterOps, - graph::{ - graph::{ - assert_edges_equal, assert_graph_equal, assert_node_equal, assert_nodes_equal, - }, - views::property_filter::PropertyRef, - }, - }, - prelude::*, - test_utils::{build_edge_list, build_graph_from_edge_list, build_window}, - }; - use proptest::{arbitrary::any, proptest}; - - fn build_filtered_graph( - edges: &[(u64, u64, i64, String, i64)], - filter: impl Fn(i64) -> bool, - ) -> Graph { - let g = Graph::new(); - for (src, dst, t, str_prop, int_prop) in edges { - g.add_node(*t, *src, NO_PROPS, None).unwrap(); - g.add_node(*t, *dst, NO_PROPS, None).unwrap(); - if filter(*int_prop) { - g.add_edge( - *t, - *src, - *dst, - [ - ("str_prop", str_prop.into()), - ("int_prop", Prop::I64(*int_prop)), - ], - None, - ) - .unwrap(); - } - } - g - } - - #[test] - fn test_filter_gt() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges( - PropertyFilter::gt(PropertyRef::Property("int_prop".to_string()), v) - ).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv > v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_one_edge() { - let g = Graph::new(); - g.add_edge(0, 1, 2, [("int_prop", 0i64)], None).unwrap(); - let filtered = g - .filter_exploded_edges(PropertyFilter::gt( - PropertyRef::Property("int_prop".to_string()), - 1i64, - )) - .unwrap(); - let gf = Graph::new(); - gf.add_node(0, 1, NO_PROPS, None).unwrap(); - gf.add_node(0, 2, NO_PROPS, None).unwrap(); - - assert_graph_equal(&filtered, &gf); - } - - #[test] - fn test_filter_ge() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::ge(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv >= v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_filter_lt() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges( PropertyFilter::lt(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv < v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_filter_le() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::le(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv <= v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_filter_eq() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_filter_ne() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges( PropertyFilter::ne(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv != v); - assert_graph_equal(&filtered, &expected_filtered_g); - }) - } - - #[test] - fn test_filter_window() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::(), (start, end) in build_window() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); - assert_graph_equal(&filtered.window(start, end), &expected_filtered_g.window(start, end)); - }) - } - - #[test] - fn test_filter_materialise_is_consistent() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let mat = filtered.materialize().unwrap(); - assert_edges_equal(&filtered.edges(), &mat.edges()); - // FIXME filtered_exploded_edges doesn't propagate timestamps to nodes assert_graph_equal(&filtered, &filtered.materialize().unwrap()); - }) - } - - #[test] - fn test_filter_on_nodes() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered_nodes = g.nodes().filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); - assert_nodes_equal(&filtered_nodes, &expected_filtered_g.nodes()); - }) - } - - #[test] - fn test_filter_on_node() { - proptest!(|( - edges in build_edge_list(100, 2), v in any::() - )| { - let g = build_graph_from_edge_list(&edges); - if let Some(node) = g.node(0) { - let filtered_node = node.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); - assert_node_equal(filtered_node, expected_filtered_g.node(0).unwrap()) - } - }) - } - - #[test] - fn test_filter_materialise_window_is_consistent() { - proptest!(|( - edges in build_edge_list(100, 100), v in any::(), (start, end) in build_window() - )| { - let g = build_graph_from_edge_list(&edges); - let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); - let left = filtered.window(start, end); - let right = filtered.window(start, end).materialize().unwrap(); - assert_edges_equal(&left.edges(), &right.edges()); - // FIXME filtered_exploded_edges doesn't propagate timestamps to nodes assert_graph_equal(&filtered, &filtered.materialize().unwrap()); - }) - } - - // FIXME: Semantics for materialised graph and filtering are not implemented properly, disabled for now - // - // #[test] - // fn test_persistent_graph() { - // let g = PersistentGraph::new(); - // g.add_edge(0, 1, 2, [("int_prop", 0i64)], None).unwrap(); - // g.delete_edge(2, 1, 2, None).unwrap(); - // g.add_edge(5, 1, 2, [("int_prop", 5i64)], None).unwrap(); - // g.delete_edge(7, 1, 2, None).unwrap(); - // - // let edges = g - // .node(1) - // .unwrap() - // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) - // .unwrap() - // .edges() - // .explode() - // .collect(); - // println!("{:?}", edges); - // - // assert_eq!(edges.len(), 1); - // let gf = g - // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) - // .unwrap(); - // let gfm = gf.materialize().unwrap(); - // - // assert_graph_equal(&gf, &gfm); // check materialise is consistent - // } - // - // #[test] - // fn test_persistent_graph_materialise() { - // proptest!(|(edges in build_edge_list(100, 100), edge_deletions in build_edge_deletions(100, 100), v in any::())| { - // let g = build_graph_from_edge_list(&edges); - // let g = g.persistent_graph(); - // for (src, dst, t) in edge_deletions { - // g.delete_edge(t, src, dst, None).unwrap(); - // } - // let gf = g - // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)) - // .unwrap(); - // let gfm = gf.materialize().unwrap(); - // assert_graph_equal(&gf, &gfm) - // }) - // } - // - // #[test] - // fn test_persistent_graph_materialise_window() { - // proptest!(|(edges in build_edge_list(100, 100), edge_deletions in build_edge_deletions(100, 100), v in any::(), (start, end) in build_window())| { - // let g = build_graph_from_edge_list(&edges); - // let g = g.persistent_graph(); - // for (src, dst, t) in edge_deletions { - // if let Some(earliest) = g.edge(src, dst).and_then(|e| e.earliest_time()) { - // // FIXME: many known bugs with the windowing semantics for edges that are deleted first so don't make them for now - // if earliest <= t { - // g.delete_edge(t, src, dst, None).unwrap(); - // } - // } - // - // } - // let gwf = g.window(start, end) - // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)) - // .unwrap(); - // - // - // let gwfm = gwf.materialize().unwrap(); - // assert_graph_equal(&gwf, &gwfm); - // - // let gfw = g - // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)).unwrap() - // .window(start, end); - // let gfwm = gfw.materialize().unwrap(); - // assert_graph_equal(&gfw, &gfwm); - // }) - // } - // - // #[test] - // fn test_persistent_graph_only_deletion() { - // let g = PersistentGraph::new(); - // g.delete_edge(0, 0, 0, None).unwrap(); - // let gfw = g - // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) - // .unwrap() - // .window(-1, 1); - // println!( - // "earliest: {:?}, latest: {:?}", - // gfw.earliest_time(), - // gfw.latest_time() - // ); - // let gfwm = gfw.materialize().unwrap(); - // println!( - // "earliest: {:?}, latest: {:?}", - // gfwm.earliest_time(), - // gfwm.latest_time() - // ); - // assert!(gfw.node(0).is_some()); - // assert!(gfwm.node(0).is_some()); - // assert_eq!(gfw.earliest_time(), None); - // } -} +// use crate::{ +// core::utils::errors::GraphError, +// db::{ +// api::view::internal::{InternalMaterialize, OneHopFilter}, +// graph::views::filter::internal::InternalExplodedEdgeFilterOps, +// }, +// prelude::GraphViewOps, +// }; +// use raphtory_api::GraphType; +// +// pub trait ExplodedEdgePropertyFilterOps<'graph>: OneHopFilter<'graph> { +// fn filter_exploded_edges( +// &self, +// filter: F, +// ) -> Result>, GraphError> +// { +// if matches!( +// self.current_filter().graph_type(), +// GraphType::PersistentGraph +// ) { +// return Err(GraphError::PropertyFilteringNotImplemented); +// } +// let graph = filter.create_exploded_edge_filter(self.current_filter().clone())?; +// Ok(self.one_hop_filtered(graph)) +// } +// } +// +// impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilterOps<'graph> for G {} +// +// #[cfg(test)] +// mod test { +// use crate::{ +// db::{ +// api::view::exploded_edge_property_filter::ExplodedEdgePropertyFilterOps, +// graph::{ +// graph::{ +// assert_edges_equal, assert_graph_equal, assert_node_equal, assert_nodes_equal, +// }, +// views::filter::model::property_filter::{PropertyFilter, PropertyRef}, +// }, +// }, +// prelude::*, +// test_utils::{build_edge_list, build_graph_from_edge_list, build_window}, +// }; +// use proptest::{arbitrary::any, proptest}; +// +// fn build_filtered_graph( +// edges: &[(u64, u64, i64, String, i64)], +// filter: impl Fn(i64) -> bool, +// ) -> Graph { +// let g = Graph::new(); +// for (src, dst, t, str_prop, int_prop) in edges { +// g.add_node(*t, *src, NO_PROPS, None).unwrap(); +// g.add_node(*t, *dst, NO_PROPS, None).unwrap(); +// if filter(*int_prop) { +// g.add_edge( +// *t, +// *src, +// *dst, +// [ +// ("str_prop", str_prop.into()), +// ("int_prop", Prop::I64(*int_prop)), +// ], +// None, +// ) +// .unwrap(); +// } +// } +// g +// } +// +// #[test] +// fn test_filter_gt() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges( +// PropertyFilter::gt(PropertyRef::Property("int_prop".to_string()), v) +// ).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv > v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_one_edge() { +// let g = Graph::new(); +// g.add_edge(0, 1, 2, [("int_prop", 0i64)], None).unwrap(); +// let filtered = g +// .filter_exploded_edges(PropertyFilter::gt( +// PropertyRef::Property("int_prop".to_string()), +// 1i64, +// )) +// .unwrap(); +// let gf = Graph::new(); +// gf.add_node(0, 1, NO_PROPS, None).unwrap(); +// gf.add_node(0, 2, NO_PROPS, None).unwrap(); +// +// assert_graph_equal(&filtered, &gf); +// } +// +// #[test] +// fn test_filter_ge() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::ge(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv >= v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_filter_lt() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges( PropertyFilter::lt(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv < v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_filter_le() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::le(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv <= v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_filter_eq() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_filter_ne() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges( PropertyFilter::ne(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv != v); +// assert_graph_equal(&filtered, &expected_filtered_g); +// }) +// } +// +// #[test] +// fn test_filter_window() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::(), (start, end) in build_window() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); +// assert_graph_equal(&filtered.window(start, end), &expected_filtered_g.window(start, end)); +// }) +// } +// +// #[test] +// fn test_filter_materialise_is_consistent() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let mat = filtered.materialize().unwrap(); +// assert_edges_equal(&filtered.edges(), &mat.edges()); +// // FIXME filtered_exploded_edges doesn't propagate timestamps to nodes assert_graph_equal(&filtered, &filtered.materialize().unwrap()); +// }) +// } +// +// #[test] +// fn test_filter_on_nodes() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered_nodes = g.nodes().filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); +// assert_nodes_equal(&filtered_nodes, &expected_filtered_g.nodes()); +// }) +// } +// +// #[test] +// fn test_filter_on_node() { +// proptest!(|( +// edges in build_edge_list(100, 2), v in any::() +// )| { +// let g = build_graph_from_edge_list(&edges); +// if let Some(node) = g.node(0) { +// let filtered_node = node.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let expected_filtered_g = build_filtered_graph(&edges, |vv| vv == v); +// assert_node_equal(filtered_node, expected_filtered_g.node(0).unwrap()) +// } +// }) +// } +// +// #[test] +// fn test_filter_materialise_window_is_consistent() { +// proptest!(|( +// edges in build_edge_list(100, 100), v in any::(), (start, end) in build_window() +// )| { +// let g = build_graph_from_edge_list(&edges); +// let filtered = g.filter_exploded_edges(PropertyFilter::eq(PropertyRef::Property("int_prop".to_string()), v)).unwrap(); +// let left = filtered.window(start, end); +// let right = filtered.window(start, end).materialize().unwrap(); +// assert_edges_equal(&left.edges(), &right.edges()); +// // FIXME filtered_exploded_edges doesn't propagate timestamps to nodes assert_graph_equal(&filtered, &filtered.materialize().unwrap()); +// }) +// } +// +// // FIXME: Semantics for materialised graph and filtering are not implemented properly, disabled for now +// // +// // #[test] +// // fn test_persistent_graph() { +// // let g = PersistentGraph::new(); +// // g.add_edge(0, 1, 2, [("int_prop", 0i64)], None).unwrap(); +// // g.delete_edge(2, 1, 2, None).unwrap(); +// // g.add_edge(5, 1, 2, [("int_prop", 5i64)], None).unwrap(); +// // g.delete_edge(7, 1, 2, None).unwrap(); +// // +// // let edges = g +// // .node(1) +// // .unwrap() +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) +// // .unwrap() +// // .edges() +// // .explode() +// // .collect(); +// // println!("{:?}", edges); +// // +// // assert_eq!(edges.len(), 1); +// // let gf = g +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) +// // .unwrap(); +// // let gfm = gf.materialize().unwrap(); +// // +// // assert_graph_equal(&gf, &gfm); // check materialise is consistent +// // } +// // +// // #[test] +// // fn test_persistent_graph_materialise() { +// // proptest!(|(edges in build_edge_list(100, 100), edge_deletions in build_edge_deletions(100, 100), v in any::())| { +// // let g = build_graph_from_edge_list(&edges); +// // let g = g.persistent_graph(); +// // for (src, dst, t) in edge_deletions { +// // g.delete_edge(t, src, dst, None).unwrap(); +// // } +// // let gf = g +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)) +// // .unwrap(); +// // let gfm = gf.materialize().unwrap(); +// // assert_graph_equal(&gf, &gfm) +// // }) +// // } +// // +// // #[test] +// // fn test_persistent_graph_materialise_window() { +// // proptest!(|(edges in build_edge_list(100, 100), edge_deletions in build_edge_deletions(100, 100), v in any::(), (start, end) in build_window())| { +// // let g = build_graph_from_edge_list(&edges); +// // let g = g.persistent_graph(); +// // for (src, dst, t) in edge_deletions { +// // if let Some(earliest) = g.edge(src, dst).and_then(|e| e.earliest_time()) { +// // // FIXME: many known bugs with the windowing semantics for edges that are deleted first so don't make them for now +// // if earliest <= t { +// // g.delete_edge(t, src, dst, None).unwrap(); +// // } +// // } +// // +// // } +// // let gwf = g.window(start, end) +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)) +// // .unwrap(); +// // +// // +// // let gwfm = gwf.materialize().unwrap(); +// // assert_graph_equal(&gwf, &gwfm); +// // +// // let gfw = g +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", v)).unwrap() +// // .window(start, end); +// // let gfwm = gfw.materialize().unwrap(); +// // assert_graph_equal(&gfw, &gfwm); +// // }) +// // } +// // +// // #[test] +// // fn test_persistent_graph_only_deletion() { +// // let g = PersistentGraph::new(); +// // g.delete_edge(0, 0, 0, None).unwrap(); +// // let gfw = g +// // .filter_exploded_edges(PropertyFilter::gt("int_prop", 1i64)) +// // .unwrap() +// // .window(-1, 1); +// // println!( +// // "earliest: {:?}, latest: {:?}", +// // gfw.earliest_time(), +// // gfw.latest_time() +// // ); +// // let gfwm = gfw.materialize().unwrap(); +// // println!( +// // "earliest: {:?}, latest: {:?}", +// // gfwm.earliest_time(), +// // gfwm.latest_time() +// // ); +// // assert!(gfw.node(0).is_some()); +// // assert!(gfwm.node(0).is_some()); +// // assert_eq!(gfw.earliest_time(), None); +// // } +// } diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index bb3e6c9015..0c98dcc37d 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -17,19 +17,23 @@ use crate::{ view::{internal::*, *}, }, graph::{ + create_node_type_filter, edge::EdgeView, edges::Edges, node::NodeView, nodes::Nodes, views::{ - cached_view::CachedView, node_subgraph::NodeSubgraph, - node_type_filtered_subgraph::TypeFilteredSubgraph, property_filter::FilterExpr, + cached_view::CachedView, + filter::{ + model::{AsEdgeFilter, AsNodeFilter}, + node_type_filtered_graph::NodeTypeFilteredGraph, + }, + node_subgraph::NodeSubgraph, }, }, }, }; use chrono::{DateTime, Utc}; -use itertools::Itertools; use raphtory_api::{ atomic_extra::atomic_usize_from_mut_slice, core::{ @@ -41,9 +45,12 @@ use raphtory_api::{ use rayon::prelude::*; use rustc_hash::FxHashSet; use std::{ - borrow::Borrow, + fs::File, + path::{Path, PathBuf}, sync::{atomic::Ordering, Arc}, }; +#[cfg(feature = "search")] +use zip::ZipArchive; /// This trait GraphViewOps defines operations for accessing /// information about a graph. The trait has associated types @@ -69,10 +76,10 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { fn cache_view(&self) -> CachedView; - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: AsRef>( &self, nodes_types: I, - ) -> TypeFilteredSubgraph; + ) -> NodeTypeFilteredGraph; fn exclude_nodes, V: AsNodeRef>( &self, @@ -133,16 +140,24 @@ pub trait GraphViewOps<'graph>: BoxableGraphView + Sized + Clone + 'graph { pub trait SearchableGraphOps: Sized { fn create_index(&self) -> Result<(), GraphError>; - fn search_nodes( + fn create_index_in_ram(&self) -> Result<(), GraphError>; + + fn load_index(&self, path: &PathBuf) -> Result<(), GraphError>; + + fn persist_index_to_disk(&self, path: &PathBuf) -> Result<(), GraphError>; + + fn persist_index_to_disk_zip(&self, path: &PathBuf) -> Result<(), GraphError>; + + fn search_nodes( &self, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, ) -> Result>, GraphError>; - fn search_edges( + fn search_edges( &self, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, ) -> Result>, GraphError>; @@ -381,16 +396,13 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> CachedView::new(self.clone()) } - fn subgraph_node_types, V: Borrow>( + fn subgraph_node_types, V: AsRef>( &self, - nodes_types: I, - ) -> TypeFilteredSubgraph { - let meta = self.node_meta().node_type_meta(); - let r = nodes_types - .into_iter() - .flat_map(|nt| meta.get_id(nt.borrow())) - .collect_vec(); - TypeFilteredSubgraph::new(self.clone(), r) + 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) } fn exclude_nodes, V: AsNodeRef>(&self, nodes: I) -> NodeSubgraph { @@ -437,7 +449,7 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> .par_iter() .filter(move |v| self.filter_node(*v, layer_ids)) .count(), - NodeList::List { nodes } => nodes + NodeList::List { elems } => elems .par_iter() .filter(move |&id| self.filter_node(core_nodes.node_entry(id), layer_ids)) .count(), @@ -619,15 +631,78 @@ impl<'graph, G: BoxableGraphView + Sized + Clone + 'graph> GraphViewOps<'graph> impl SearchableGraphOps for G { fn create_index(&self) -> Result<(), GraphError> { self.get_storage() - .map_or(Err(GraphError::FailedToCreateIndex), |storage| { - storage.get_or_create_index()?; + .map_or(Err(GraphError::IndexingNotSupported), |storage| { + storage.get_or_create_index(None)?; + Ok(()) + }) + } + + fn create_index_in_ram(&self) -> Result<(), GraphError> { + self.get_storage() + .map_or(Err(GraphError::IndexingNotSupported), |storage| { + storage.get_or_create_index_in_ram()?; + Ok(()) + }) + } + + fn load_index(&self, path: &PathBuf) -> Result<(), GraphError> { + fn has_index>(zip_path: P) -> Result { + let file = File::open(&zip_path)?; + let mut archive = ZipArchive::new(file)?; + + for i in 0..archive.len() { + let entry = archive.by_index(i)?; + let entry_path = Path::new(entry.name()); + + if let Some(first_component) = entry_path.components().next() { + if first_component.as_os_str() == "index" { + return Ok(true); + } + } + } + + Ok(false) + } + + self.get_storage() + .map_or(Err(GraphError::IndexingNotSupported), |storage| { + if path.is_file() { + if has_index(path)? { + storage.get_or_create_index(Some(path.clone()))?; + } else { + return Ok(()); // Skip if no index in zip + } + } else { + let index_path = path.join("index"); + if index_path.exists() && index_path.read_dir()?.next().is_some() { + storage.get_or_create_index(Some(index_path.clone()))?; + } + } + + Ok(()) + }) + } + + fn persist_index_to_disk(&self, path: &PathBuf) -> Result<(), GraphError> { + let path = path.join("index"); + self.get_storage() + .map_or(Err(GraphError::IndexingNotSupported), |storage| { + storage.persist_index_to_disk(&path)?; + Ok(()) + }) + } + + fn persist_index_to_disk_zip(&self, path: &PathBuf) -> Result<(), GraphError> { + self.get_storage() + .map_or(Err(GraphError::IndexingNotSupported), |storage| { + storage.persist_index_to_disk_zip(&path)?; Ok(()) }) } - fn search_nodes( + fn search_nodes( &self, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, ) -> Result>, GraphError> { @@ -638,9 +713,9 @@ impl SearchableGraphOps for G { index.searcher().search_nodes(self, filter, limit, offset) } - fn search_edges( + fn search_edges( &self, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, ) -> Result>, GraphError> { diff --git a/raphtory/src/db/api/view/internal/into_dynamic.rs b/raphtory/src/db/api/view/internal/into_dynamic.rs index 3fde32c8c3..423b7e13ac 100644 --- a/raphtory/src/db/api/view/internal/into_dynamic.rs +++ b/raphtory/src/db/api/view/internal/into_dynamic.rs @@ -1,7 +1,8 @@ use crate::db::api::view::{ internal::{DynamicGraph, OneHopFilter, Static}, - StaticGraphViewOps, + BoxableGraphView, StaticGraphViewOps, }; +use std::sync::Arc; pub trait IntoDynamic: 'static { fn into_dynamic(self) -> DynamicGraph; @@ -19,6 +20,12 @@ impl IntoDynamic for DynamicGraph { } } +impl IntoDynamic for Arc { + fn into_dynamic(self) -> DynamicGraph { + DynamicGraph(self) + } +} + pub trait IntoDynHop: OneHopFilter<'static, FilteredGraph: IntoDynamic> { fn into_dyn_hop(self) -> Self::Filtered; } diff --git a/raphtory/src/db/api/view/internal/list_ops.rs b/raphtory/src/db/api/view/internal/list_ops.rs index 8ad69df877..faaeaede1b 100644 --- a/raphtory/src/db/api/view/internal/list_ops.rs +++ b/raphtory/src/db/api/view/internal/list_ops.rs @@ -4,7 +4,7 @@ use crate::{ }; use enum_dispatch::enum_dispatch; use rayon::{iter::Either, prelude::*}; -use std::sync::Arc; +use std::hash::Hash; #[enum_dispatch] pub trait ListOps { @@ -28,100 +28,81 @@ where } } -#[derive(Debug, Clone)] -pub enum NodeList { - All { num_nodes: usize }, - List { nodes: Index }, +#[derive(Debug)] +pub enum List { + All { len: usize }, + List { elems: Index }, } -impl NodeList { - pub fn par_iter(&self) -> impl IndexedParallelIterator + '_ { - match self { - NodeList::All { num_nodes } => Either::Left((0..*num_nodes).into_par_iter().map(VID)), - NodeList::List { nodes } => Either::Right(nodes.par_iter()), - } - } - - pub fn into_par_iter(self) -> impl IndexedParallelIterator { - match self { - NodeList::All { num_nodes } => Either::Left((0..num_nodes).into_par_iter().map(VID)), - NodeList::List { nodes } => Either::Right(nodes.into_par_iter()), - } - } - - pub fn iter(&self) -> impl Iterator + '_ { - match self { - NodeList::All { num_nodes } => Either::Left((0..*num_nodes).map(VID)), - NodeList::List { nodes } => Either::Right(nodes.iter()), - } - } +pub type NodeList = List; +pub type EdgeList = List; - pub fn len(&self) -> usize { +impl Clone for List { + fn clone(&self) -> Self { match self { - NodeList::All { num_nodes } => *num_nodes, - NodeList::List { nodes } => nodes.len(), + List::All { len } => List::All { len: *len }, + List::List { elems } => List::List { + elems: elems.clone(), + }, } } } -impl IntoIterator for NodeList { - type Item = VID; - type IntoIter = Box + Send + Sync>; - - fn into_iter(self) -> Self::IntoIter { - match self { - NodeList::All { num_nodes } => Box::new((0..num_nodes).map(VID)), - NodeList::List { nodes } => Box::new(nodes.into_iter()), +impl + From + Send + Sync> List { + pub fn intersection(&self, other: &List) -> List { + match (self, other) { + (List::All { len: a }, List::All { len: b }) => { + let len = *a.min(b); + List::All { len } + } + (List::List { .. }, List::All { .. }) => self.clone(), + (List::All { .. }, List::List { .. }) => other.clone(), + (List::List { elems: a }, List::List { elems: b }) => { + let elems = a.intersection(b); + List::List { elems } + } } } -} - -#[derive(Clone)] -pub enum EdgeList { - All { num_edges: usize }, - List { edges: Arc<[EID]> }, -} -impl EdgeList { - pub fn par_iter(&self) -> impl IndexedParallelIterator + '_ { + pub fn par_iter(&self) -> impl IndexedParallelIterator + '_ { match self { - EdgeList::All { num_edges } => Either::Left((0..*num_edges).into_par_iter().map(EID)), - EdgeList::List { edges } => Either::Right(edges.par_iter().copied()), + List::All { len } => Either::Left((0..*len).into_par_iter().map(From::from)), + List::List { elems } => Either::Right(elems.par_iter()), } } - pub fn into_par_iter(self) -> impl IndexedParallelIterator { + pub fn into_par_iter(self) -> impl IndexedParallelIterator { match self { - EdgeList::All { num_edges } => Either::Left((0..num_edges).into_par_iter().map(EID)), - EdgeList::List { edges } => { - Either::Right((0..edges.len()).into_par_iter().map(move |i| edges[i])) - } + List::All { len } => Either::Left((0..len).into_par_iter().map(From::from)), + List::List { elems } => Either::Right(elems.into_par_iter()), } } - pub fn iter(&self) -> impl Iterator + '_ { + pub fn iter(&self) -> impl Iterator + '_ { match self { - EdgeList::All { num_edges } => Either::Left((0..*num_edges).map(EID)), - EdgeList::List { edges } => Either::Right(edges.iter().copied()), + List::All { len } => Either::Left((0..*len).map(From::from)), + List::List { elems } => Either::Right(elems.iter()), } } pub fn len(&self) -> usize { match self { - EdgeList::All { num_edges } => *num_edges, - EdgeList::List { edges } => edges.len(), + List::All { len } => *len, + List::List { elems } => elems.len(), } } } -impl IntoIterator for EdgeList { - type Item = EID; +impl + From + Send + Sync + 'static> IntoIterator + for List +{ + type Item = I; type IntoIter = Box + Send + Sync>; fn into_iter(self) -> Self::IntoIter { match self { - EdgeList::All { num_edges } => Box::new((0..num_edges).map(EID)), - EdgeList::List { edges } => Box::new((0..edges.len()).map(move |i| edges[i])), + List::All { len } => Box::new((0..len).map(From::from)), + List::List { elems } => Box::new(elems.into_iter()), } } } diff --git a/raphtory/src/db/api/view/internal/wrapped_graph.rs b/raphtory/src/db/api/view/internal/wrapped_graph.rs index 3fd2f4eb6c..c1550ac2f1 100644 --- a/raphtory/src/db/api/view/internal/wrapped_graph.rs +++ b/raphtory/src/db/api/view/internal/wrapped_graph.rs @@ -5,10 +5,10 @@ use std::sync::Arc; use crate::db::api::view::internal::InheritStorageOps; -impl InheritViewOps for Arc {} +impl<'graph> InheritViewOps for Arc {} -impl InheritStorageOps for Arc {} +impl<'graph> InheritStorageOps for Arc {} -impl InheritNodeHistoryFilter for Arc {} +impl<'graph> InheritNodeHistoryFilter for Arc {} -impl InheritEdgeHistoryFilter for Arc {} +impl<'graph> InheritEdgeHistoryFilter for Arc {} diff --git a/raphtory/src/db/api/view/mod.rs b/raphtory/src/db/api/view/mod.rs index 56ca68713a..7e79b8f779 100644 --- a/raphtory/src/db/api/view/mod.rs +++ b/raphtory/src/db/api/view/mod.rs @@ -2,7 +2,7 @@ mod edge; mod edge_property_filter; -mod exploded_edge_property_filter; +// mod exploded_edge_property_filter; pub(crate) mod graph; pub mod internal; mod layer; @@ -15,7 +15,7 @@ pub(crate) use edge::BaseEdgeViewOps; pub use edge::EdgeViewOps; pub use edge_property_filter::EdgePropertyFilterOps; -pub use exploded_edge_property_filter::ExplodedEdgePropertyFilterOps; +// pub use exploded_edge_property_filter::ExplodedEdgePropertyFilterOps; pub use graph::*; pub use internal::{ Base, BoxableGraphView, DynamicGraph, InheritViewOps, IntoDynHop, IntoDynamic, diff --git a/raphtory/src/db/api/view/node_property_filter.rs b/raphtory/src/db/api/view/node_property_filter.rs index 4734216605..68dd842e5b 100644 --- a/raphtory/src/db/api/view/node_property_filter.rs +++ b/raphtory/src/db/api/view/node_property_filter.rs @@ -1,20 +1,17 @@ use crate::{ core::utils::errors::GraphError, db::{ - api::view::internal::OneHopFilter, - graph::views::property_filter::internal::InternalNodePropertyFilterOps, + api::view::internal::OneHopFilter, graph::views::filter::internal::InternalNodeFilterOps, }, prelude::GraphViewOps, }; pub trait NodePropertyFilterOps<'graph>: OneHopFilter<'graph> { - fn filter_nodes( + fn filter_nodes( &self, filter: F, - ) -> Result>, GraphError> - { - Ok(self - .one_hop_filtered(filter.create_node_property_filter(self.current_filter().clone())?)) + ) -> Result>, GraphError> { + Ok(self.one_hop_filtered(filter.create_node_filter(self.current_filter().clone())?)) } } @@ -25,7 +22,10 @@ mod test { use crate::{ db::graph::{ graph::assert_edges_equal, - views::property_filter::{PropertyFilter, PropertyRef}, + views::filter::model::{ + property_filter::{PropertyFilter, PropertyRef}, + ComposableFilter, NodeFilter, NodeFilterBuilderOps, PropertyFilterOps, + }, }, prelude::*, test_utils::{ @@ -36,6 +36,31 @@ mod test { use itertools::Itertools; use proptest::{arbitrary::any, proptest}; + #[test] + fn test_node_filter_on_nodes() { + let g = Graph::new(); + g.add_node(0, "Jimi", [("band", "JH Experience")], None) + .unwrap(); + g.add_node(1, "John", [("band", "Dead & Company")], None) + .unwrap(); + g.add_node(2, "David", [("band", "Pink Floyd")], None) + .unwrap(); + + // let filter_expr = NodeFilter::name().eq("Jimi"); + let filter_expr = NodeFilter::name() + .eq("John") + // .and(PropertyFilter::property("band").eq("Dead & Company")) + .and(PropertyFilter::property("band").eq("Dead & Company")); + // let filter_expr = CompositeNodeFilter::Node(Filter::eq("node_name", "Jimi")); + let filtered_nodes = g.nodes().filter_nodes(filter_expr).unwrap(); + + assert_eq!( + filtered_nodes.iter().map(|n| n.name()).collect::>(), + // vec!["Jimi"] + vec!["John"] + ); + } + #[test] fn test_node_property_filter_on_nodes() { let g = Graph::new(); diff --git a/raphtory/src/db/graph/graph.rs b/raphtory/src/db/graph/graph.rs index 2503f30405..d73f1f1624 100644 --- a/raphtory/src/db/graph/graph.rs +++ b/raphtory/src/db/graph/graph.rs @@ -450,6 +450,8 @@ impl Graph { #[cfg(test)] mod db_tests { use super::*; + #[cfg(feature = "proto")] + use crate::serialise::StableDecode; use crate::{ algorithms::components::weakly_connected_components, core::{ diff --git a/raphtory/src/db/graph/mod.rs b/raphtory/src/db/graph/mod.rs index fceef2c402..00ad054646 100644 --- a/raphtory/src/db/graph/mod.rs +++ b/raphtory/src/db/graph/mod.rs @@ -9,9 +9,9 @@ pub mod nodes; pub mod path; pub mod views; -pub(crate) fn create_node_type_filter( +pub(crate) fn create_node_type_filter, V: AsRef>( dict_mapper: &DictMapper, - node_types: &[impl AsRef], + node_types: I, ) -> Arc<[bool]> { let len = dict_mapper.len(); let mut bool_arr = vec![false; len]; diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index a958a47734..aac3c293f3 100644 --- a/raphtory/src/db/graph/node.rs +++ b/raphtory/src/db/graph/node.rs @@ -150,10 +150,10 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgePropertyFilt for NodeView { } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> - ExplodedEdgePropertyFilterOps<'graph> for NodeView -{ -} +// impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> +// ExplodedEdgePropertyFilterOps<'graph> for NodeView +// { +// } impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodePropertyFilterOps<'graph> for NodeView diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 96b585925b..769516f016 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -132,13 +132,13 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgePropertyFilt for Nodes<'graph, G, GH> { } -impl< - 'graph, - G: GraphViewOps<'graph>, - GH: GraphViewOps<'graph> + ExplodedEdgePropertyFilterOps<'graph>, - > ExplodedEdgePropertyFilterOps<'graph> for Nodes<'graph, G, GH> -{ -} +// impl< +// 'graph, +// G: GraphViewOps<'graph>, +// GH: GraphViewOps<'graph> + ExplodedEdgePropertyFilterOps<'graph>, +// > ExplodedEdgePropertyFilterOps<'graph> for Nodes<'graph, G, GH> +// { +// } impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodePropertyFilterOps<'graph> for Nodes<'graph, G, GH> @@ -248,7 +248,10 @@ where }) } - pub fn type_filter(&self, node_types: &[impl AsRef]) -> Nodes<'graph, G, GH> { + pub fn type_filter, V: AsRef>( + &self, + node_types: I, + ) -> Nodes<'graph, G, GH> { let node_types_filter = Some(create_node_type_filter( self.graph.node_meta().node_type_meta(), node_types, diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index 48f558f57c..3e71913bc2 100644 --- a/raphtory/src/db/graph/path.rs +++ b/raphtory/src/db/graph/path.rs @@ -13,7 +13,7 @@ use crate::{ edges::{Edges, NestedEdges}, node::NodeView, views::{ - layer_graph::LayeredGraph, node_type_filtered_subgraph::TypeFilteredSubgraph, + filter::node_type_filtered_graph::NodeTypeFilteredGraph, layer_graph::LayeredGraph, window_graph::WindowedGraph, }, }, @@ -34,10 +34,10 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgePropertyFilt for PathFromGraph<'graph, G, GH> { } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> - ExplodedEdgePropertyFilterOps<'graph> for PathFromGraph<'graph, G, GH> -{ -} +// impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> +// ExplodedEdgePropertyFilterOps<'graph> for PathFromGraph<'graph, G, GH> +// { +// } impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodePropertyFilterOps<'graph> for PathFromGraph<'graph, G, GH> @@ -113,7 +113,10 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> PathFromGraph<'g self.iter_refs().next().is_none() } - pub fn type_filter(&self, node_types: &[impl AsRef]) -> PathFromGraph<'graph, G, GH> { + pub fn type_filter, V: AsRef>( + &self, + node_types: I, + ) -> PathFromGraph<'graph, G, GH> { let node_types_filter = create_node_type_filter(self.graph.node_meta().node_type_meta(), node_types); @@ -283,11 +286,11 @@ impl From>> } } -impl From>> +impl From>> for PathFromNode<'static, DynamicGraph, DynamicGraph> { fn from( - value: PathFromNode<'static, DynamicGraph, TypeFilteredSubgraph>, + value: PathFromNode<'static, DynamicGraph, NodeTypeFilteredGraph>, ) -> Self { PathFromNode::new(DynamicGraph::new(value.graph.clone()), move || (value.op)()) } @@ -304,10 +307,10 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgePropertyFilt for PathFromNode<'graph, G, GH> { } -impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> - ExplodedEdgePropertyFilterOps<'graph> for PathFromNode<'graph, G, GH> -{ -} +// impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> +// ExplodedEdgePropertyFilterOps<'graph> for PathFromNode<'graph, G, GH> +// { +// } impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodePropertyFilterOps<'graph> for PathFromNode<'graph, G, GH> @@ -363,7 +366,10 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> PathFromNode<'gr self.iter().next().is_none() } - pub fn type_filter(&self, node_types: &[impl AsRef]) -> PathFromNode<'graph, G, GH> { + pub fn type_filter, V: AsRef>( + &self, + node_types: I, + ) -> PathFromNode<'graph, G, GH> { let node_types_filter = create_node_type_filter(self.graph.node_meta().node_type_meta(), node_types); diff --git a/raphtory/src/db/graph/views/cached_view.rs b/raphtory/src/db/graph/views/cached_view.rs index c126c6d2c3..8a49d68afd 100644 --- a/raphtory/src/db/graph/views/cached_view.rs +++ b/raphtory/src/db/graph/views/cached_view.rs @@ -288,297 +288,168 @@ mod test { }) } - #[cfg(all(test, feature = "search"))] - mod search_nodes_cached_view_graph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, + #[cfg(test)] + mod test_filters_cached_view { + mod test_nodes_filters_cached_view_graph { + use crate::{ + assert_filter_nodes_results, assert_filter_nodes_results_pg_w, + assert_filter_nodes_results_w, assert_search_nodes_results, + assert_search_nodes_results_pg_w, assert_search_nodes_results_w, + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, filter::model::PropertyFilterOps, + }, }, - }, - prelude::{AdditionOps, Graph, NodeViewOps, PropertyFilter, TimeOps}, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - graph - .add_node(6, "N1", [("p1", Prop::U64(2u64))], Some("air_nomad")) - .unwrap(); - graph - .add_node(7, "N1", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - - graph - .add_node(6, "N2", [("p1", Prop::U64(1u64))], Some("water_tribe")) - .unwrap(); - graph - .add_node(7, "N2", [("p1", Prop::U64(2u64))], Some("water_tribe")) - .unwrap(); - - graph - .add_node(8, "N3", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - - graph - .add_node(9, "N4", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - - graph - .add_node(5, "N5", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - graph - .add_node(6, "N5", [("p1", Prop::U64(2u64))], Some("air_nomad")) - .unwrap(); - - graph - .add_node(5, "N6", [("p1", Prop::U64(1u64))], Some("fire_nation")) - .unwrap(); - graph - .add_node(6, "N6", [("p1", Prop::U64(1u64))], Some("fire_nation")) - .unwrap(); - - graph - .add_node(3, "N7", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - graph - .add_node(5, "N7", [("p1", Prop::U64(1u64))], Some("air_nomad")) - .unwrap(); - - graph - .add_node(3, "N8", [("p1", Prop::U64(1u64))], Some("fire_nation")) - .unwrap(); - graph - .add_node(4, "N8", [("p1", Prop::U64(2u64))], Some("fire_nation")) - .unwrap(); - - graph - } - - fn search_nodes_by_composite_filter( - graph: &G, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let cv = graph.cache_view(); - let mut results = cv - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } - - fn search_nodes_by_composite_filter_w( - graph: &G, - w: Range, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let cv = graph.cache_view(); - let mut results = cv - .window(w.start, w.end) - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } - - #[test] - fn test_search_nodes_cached_view_graph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - } - - #[test] - fn test_search_nodes_cached_view_graph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); + prelude::{AdditionOps, Graph, PropertyFilter}, + }; + + use crate::db::graph::views::test_helpers::filter_nodes_with; + + #[cfg(feature = "storage")] + use tempfile::TempDir; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + + fn init_graph(graph: G) -> G { + let node_data = vec![ + (6, "N1", 2u64, "air_nomad"), + (7, "N1", 1u64, "air_nomad"), + (6, "N2", 1u64, "water_tribe"), + (7, "N2", 2u64, "water_tribe"), + (8, "N3", 1u64, "air_nomad"), + (9, "N4", 1u64, "air_nomad"), + (5, "N5", 1u64, "air_nomad"), + (6, "N5", 2u64, "air_nomad"), + (5, "N6", 1u64, "fire_nation"), + (6, "N6", 1u64, "fire_nation"), + (3, "N7", 1u64, "air_nomad"), + (5, "N7", 1u64, "air_nomad"), + (3, "N8", 1u64, "fire_nation"), + (4, "N8", 2u64, "fire_nation"), + ]; + + for (ts, name, value, kind) in node_data { + graph + .add_node(ts, name, [("p1", Prop::U64(value))], Some(kind)) + .unwrap(); + } - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter); - assert_eq!(results, vec!["N1", "N3", "N6"]); - } + graph + } - #[test] - fn test_search_nodes_persistent_cached_view_graph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + use crate::prelude::{NodeViewOps, TimeOps}; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - } + #[test] + fn test_nodes_filters() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } - #[test] - fn test_search_nodes_persistent_cached_view_graph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + #[test] + fn test_nodes_filters_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + // TODO: Fails + // assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); + #[test] + fn test_nodes_filters_pg_w() { + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2", "N5", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } } - } - #[cfg(all(test, feature = "search"))] - mod search_edges_cached_view_graph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, + mod test_edges_filter_cached_view_graph { + use crate::{ + assert_filter_edges_results, assert_filter_edges_results_w, + assert_search_edges_results, assert_search_edges_results_pg_w, + assert_search_edges_results_w, + }; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_edges_with; + + #[cfg(feature = "storage")] + use tempfile::TempDir; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, filter::model::PropertyFilterOps, + test_helpers::filter_edges_with, + }, }, - }, - prelude::{AdditionOps, EdgeViewOps, Graph, NodeViewOps, PropertyFilter, TimeOps}, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - graph - .add_edge(6, "N1", "N2", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_edge(7, "N1", "N2", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(6, "N2", "N3", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(7, "N2", "N3", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(8, "N3", "N4", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(9, "N4", "N5", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(5, "N5", "N6", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(6, "N5", "N6", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(5, "N6", "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(6, "N6", "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(3, "N7", "N8", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(5, "N7", "N8", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(3, "N8", "N1", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(4, "N8", "N1", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - } - - fn search_edges_by_composite_filter( - graph: &G, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let cv = graph.cache_view(); - let mut results = cv - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } - - fn search_edges_by_composite_filter_w( - graph: &G, - w: Range, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let cv = graph.cache_view(); - let mut results = cv - .window(w.start, w.end) - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } - - #[test] - fn test_search_edges_cached_view_graph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); - } - - #[test] - fn test_search_edges_cached_view_graph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); + prelude::{AdditionOps, Graph, PropertyFilter, TimeOps}, + }; + + fn init_graph(graph: G) -> G { + let edge_data = vec![ + (6, "N1", "N2", 2u64), + (7, "N1", "N2", 1u64), + (6, "N2", "N3", 1u64), + (7, "N2", "N3", 2u64), + (8, "N3", "N4", 1u64), + (9, "N4", "N5", 1u64), + (5, "N5", "N6", 1u64), + (6, "N5", "N6", 2u64), + (5, "N6", "N7", 1u64), + (6, "N6", "N7", 1u64), + (3, "N7", "N8", 1u64), + (5, "N7", "N8", 1u64), + (3, "N8", "N1", 1u64), + (4, "N8", "N1", 2u64), + ]; + + for (ts, src, dst, p1_val) in edge_data { + graph + .add_edge(ts, src, dst, [("p1", Prop::U64(p1_val))], None) + .unwrap(); + } - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7"]); - } + graph + } - #[test] - fn test_search_edges_persistent_cached_view_graph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); - } + #[test] + fn test_edges_filters() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } - #[test] - fn test_search_edges_persistent_cached_view_graph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + #[test] + fn test_edges_filter_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_edges_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_edges_results_w!(init_graph, filter, 6..9, expected_results); + } - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]); + #[test] + fn test_edges_filters_pg_w() { + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2->N3", "N5->N6", "N8->N1"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_edges_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_edges_results_pg_w!(init_graph, filter, 6..9, expected_results); + } } } } diff --git a/raphtory/src/db/graph/views/deletion_graph.rs b/raphtory/src/db/graph/views/deletion_graph.rs index 153edb8fe0..ce306e792b 100644 --- a/raphtory/src/db/graph/views/deletion_graph.rs +++ b/raphtory/src/db/graph/views/deletion_graph.rs @@ -988,20 +988,20 @@ mod test_deletions { fn test_nodes() { let g = PersistentGraph::new(); - g.add_edge(0, 1, 2, [("added", Prop::I64(0))], Some("assigned")) - .unwrap(); - g.add_edge(1, 1, 3, [("added", Prop::I64(0))], Some("assigned")) - .unwrap(); - g.add_edge(2, 4, 2, [("added", Prop::I64(0))], Some("has")) - .unwrap(); - g.add_edge(3, 4, 2, [("added", Prop::I64(0))], Some("has")) - .unwrap(); - g.add_edge(4, 5, 2, [("added", Prop::I64(0))], Some("blocks")) - .unwrap(); - g.add_edge(5, 4, 5, [("added", Prop::I64(0))], Some("has")) - .unwrap(); - g.add_edge(6, 6, 5, [("added", Prop::I64(0))], Some("assigned")) - .unwrap(); + let edges = [ + (0, 1, 2, "assigned"), + (1, 1, 3, "assigned"), + (2, 4, 2, "has"), + (3, 4, 2, "has"), + (4, 5, 2, "blocks"), + (5, 4, 5, "has"), + (6, 6, 5, "assigned"), + ]; + + for (time, src, dst, layer) in edges { + g.add_edge(time, src, dst, [("added", Prop::I64(0))], Some(layer)) + .unwrap(); + } let nodes = g .window(0, 1701786285758) @@ -2011,55 +2011,26 @@ mod test_node_history_filter_persistent_graph { use raphtory_api::core::storage::timeindex::TimeIndexEntry; fn init_graph(graph: G) -> G { - graph - .add_node(6, "N1", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_node(7, "N1", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_node(6, "N2", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(7, "N2", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_node(8, "N3", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_node(9, "N4", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_node(5, "N5", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(6, "N5", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_node(5, "N6", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(6, "N6", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_node(3, "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(5, "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); + let nodes = [ + (6, "N1", Prop::U64(2)), + (7, "N1", Prop::U64(1)), + (6, "N2", Prop::U64(1)), + (7, "N2", Prop::U64(2)), + (8, "N3", Prop::U64(1)), + (9, "N4", Prop::U64(1)), + (5, "N5", Prop::U64(1)), + (6, "N5", Prop::U64(2)), + (5, "N6", Prop::U64(1)), + (6, "N6", Prop::U64(1)), + (3, "N7", Prop::U64(1)), + (5, "N7", Prop::U64(1)), + (3, "N8", Prop::U64(1)), + (4, "N8", Prop::U64(2)), + ]; - graph - .add_node(3, "N8", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(4, "N8", [("p1", Prop::U64(2u64))], None) - .unwrap(); + for (time, id, prop) in nodes { + graph.add_node(time, id, [("p1", prop)], None).unwrap(); + } graph } @@ -2224,62 +2195,30 @@ mod test_edge_history_filter_persistent_graph { use raphtory_api::core::storage::timeindex::TimeIndexEntry; fn init_graph(graph: G) -> G { - graph - .add_edge(6, "N1", "N2", [("p1", Prop::U64(2u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(7, "N1", "N2", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(6, "N2", "N3", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(7, "N2", "N3", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(8, "N3", "N4", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - - graph - .add_edge(9, "N4", "N5", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - - graph - .add_edge(5, "N5", "N6", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(6, "N5", "N6", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(5, "N6", "N7", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(6, "N6", "N7", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(3, "N7", "N8", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(5, "N7", "N8", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(3, "N8", "N1", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(4, "N8", "N1", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); + let edges = [ + (6, "N1", "N2", Prop::U64(2), Some("layer1")), + (7, "N1", "N2", Prop::U64(1), Some("layer2")), + (6, "N2", "N3", Prop::U64(1), Some("layer1")), + (7, "N2", "N3", Prop::U64(2), Some("layer2")), + (8, "N3", "N4", Prop::U64(1), Some("layer1")), + (9, "N4", "N5", Prop::U64(1), Some("layer1")), + (5, "N5", "N6", Prop::U64(1), Some("layer1")), + (6, "N5", "N6", Prop::U64(2), Some("layer2")), + (5, "N6", "N7", Prop::U64(1), Some("layer1")), + (6, "N6", "N7", Prop::U64(1), Some("layer2")), + (3, "N7", "N8", Prop::U64(1), Some("layer1")), + (5, "N7", "N8", Prop::U64(1), Some("layer2")), + (3, "N8", "N1", Prop::U64(1), Some("layer1")), + (4, "N8", "N1", Prop::U64(2), Some("layer2")), + (3, "N9", "N2", Prop::U64(1), Some("layer1")), + (3, "N9", "N2", Prop::U64(2), Some("layer2")), + ]; - graph - .add_edge(3, "N9", "N2", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(3, "N9", "N2", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); + for (time, src, dst, prop, layer) in edges { + graph + .add_edge(time, src, dst, [("p1", prop)], layer) + .unwrap(); + } graph } diff --git a/raphtory/src/db/graph/views/filter/edge_and_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_and_filtered_graph.rs new file mode 100644 index 0000000000..fd19834f07 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_and_filtered_graph.rs @@ -0,0 +1,204 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::edges::edge_ref::EdgeStorageRef, + view::{ + internal::{ + EdgeFilterOps, EdgeHistoryFilter, EdgeList, Immutable, InheritCoreOps, + InheritMaterialize, InheritNodeFilterOps, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, InternalLayerOps, ListOps, NodeList, + Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalEdgeFilterOps, model::AndFilter}, + }, + prelude::{GraphViewOps, Layer}, +}; +use raphtory_api::core::{ + entities::{LayerIds, EID}, + storage::timeindex::TimeIndexEntry, +}; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct EdgeAndFilteredGraph { + graph: G, + left: L, + right: R, + layer_ids: LayerIds, +} + +impl InternalEdgeFilterOps for AndFilter { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> + = EdgeAndFilteredGraph, R::EdgeFiltered<'graph, G>> + where + Self: 'graph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let left = self.left.create_edge_filter(graph.clone())?; + let right = self.right.create_edge_filter(graph.clone())?; + let layer_ids = left.layer_ids().intersect(right.layer_ids()); + Ok(EdgeAndFilteredGraph { + graph, + left, + right, + layer_ids, + }) + } +} + +impl Base for EdgeAndFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for EdgeAndFilteredGraph {} +impl Immutable for EdgeAndFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritCoreOps for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritStorageOps for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritMaterialize for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritNodeFilterOps for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritPropertiesOps for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritTimeSemantics for EdgeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritNodeHistoryFilter + for EdgeAndFilteredGraph +{ +} + +impl InternalLayerOps for EdgeAndFilteredGraph +where + G: InternalLayerOps, +{ + fn layer_ids(&self) -> &LayerIds { + &self.layer_ids + } + + fn layer_ids_from_names(&self, key: Layer) -> Result { + Ok(self + .layer_ids + .intersect(&self.graph.layer_ids_from_names(key)?)) + } + + fn valid_layer_ids_from_names(&self, key: Layer) -> LayerIds { + self.layer_ids + .intersect(&self.graph.valid_layer_ids_from_names(key)) + } +} + +impl ListOps for EdgeAndFilteredGraph +where + L: ListOps, + R: ListOps, +{ + fn node_list(&self) -> NodeList { + let left = self.left.node_list(); + let right = self.right.node_list(); + left.intersection(&right) + } + + fn edge_list(&self) -> EdgeList { + let left = self.left.edge_list(); + let right = self.right.edge_list(); + left.intersection(&right) + } +} + +impl EdgeHistoryFilter for EdgeAndFilteredGraph +where + L: EdgeHistoryFilter, + R: EdgeHistoryFilter, +{ + fn is_edge_prop_update_available( + &self, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_edge_prop_update_available(layer_id, prop_id, edge_id, time) + && self + .right + .is_edge_prop_update_available(layer_id, prop_id, edge_id, time) + } + + fn is_edge_prop_update_available_window( + &self, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_edge_prop_update_available_window(layer_id, prop_id, edge_id, time, w.clone()) + && self + .right + .is_edge_prop_update_available_window(layer_id, prop_id, edge_id, time, w) + } + + fn is_edge_prop_update_latest( + &self, + layer_ids: &LayerIds, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_edge_prop_update_latest(layer_ids, layer_id, prop_id, edge_id, time) + && self + .right + .is_edge_prop_update_latest(layer_ids, layer_id, prop_id, edge_id, time) + } + + fn is_edge_prop_update_latest_window( + &self, + layer_ids: &LayerIds, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left.is_edge_prop_update_latest_window( + layer_ids, + layer_id, + prop_id, + edge_id, + time, + w.clone(), + ) && self + .right + .is_edge_prop_update_latest_window(layer_ids, layer_id, prop_id, edge_id, time, w) + } +} + +impl EdgeFilterOps for EdgeAndFilteredGraph { + #[inline] + fn edges_filtered(&self) -> bool { + self.left.edges_filtered() || self.right.edges_filtered() + } + + #[inline] + fn edge_list_trusted(&self) -> bool { + self.left.edge_list_trusted() && self.right.edge_list_trusted() + } + + #[inline] + fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { + self.left.filter_edge(edge.clone(), layer_ids) + && self.right.filter_edge(edge.clone(), layer_ids) + } +} 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 new file mode 100644 index 0000000000..fa518a138d --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_field_filtered_graph.rs @@ -0,0 +1,86 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::edges::edge_ref::EdgeStorageRef, + view::{ + internal::{ + EdgeFilterOps, Immutable, InheritCoreOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, + InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalEdgeFilterOps, model::Filter, EdgeFieldFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::entities::LayerIds; + +#[derive(Debug, Clone)] +pub struct EdgeFieldFilteredGraph { + graph: G, + filter: Filter, +} + +impl<'graph, G> EdgeFieldFilteredGraph { + pub(crate) fn new(graph: G, filter: Filter) -> Self { + Self { graph, filter } + } +} + +impl InternalEdgeFilterOps for EdgeFieldFilter { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> = EdgeFieldFilteredGraph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + Ok(EdgeFieldFilteredGraph::new(graph, self.0)) + } +} + +impl<'graph, G> 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>> InheritCoreOps 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>> EdgeFilterOps for EdgeFieldFilteredGraph { + #[inline] + fn edges_filtered(&self) -> bool { + true + } + + #[inline] + fn edge_list_trusted(&self) -> bool { + false + } + + #[inline] + fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { + if self.graph.filter_edge(edge, layer_ids) { + self.filter.matches_edge(&self.graph, edge) + } else { + false + } + } +} diff --git a/raphtory/src/db/graph/views/filter/edge_not_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_not_filtered_graph.rs new file mode 100644 index 0000000000..03a02371e1 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_not_filtered_graph.rs @@ -0,0 +1,82 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::edges::edge_ref::EdgeStorageRef, + view::{ + internal::{ + EdgeFilterOps, Immutable, InheritCoreOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, + InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalEdgeFilterOps, model::NotFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::entities::LayerIds; + +#[derive(Debug, Clone)] +pub struct EdgeNotFilteredGraph { + graph: G, + filter: T, +} + +impl InternalEdgeFilterOps for NotFilter { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> + = EdgeNotFilteredGraph> + where + Self: 'graph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let filter = self.0.create_edge_filter(graph.clone())?; + Ok(EdgeNotFilteredGraph { graph, filter }) + } +} + +impl Base for EdgeNotFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for EdgeNotFilteredGraph {} +impl Immutable for EdgeNotFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, T> InheritCoreOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritStorageOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritLayerOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritListOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritMaterialize for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritNodeFilterOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritPropertiesOps for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritTimeSemantics for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritNodeHistoryFilter for EdgeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritEdgeHistoryFilter for EdgeNotFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, T: EdgeFilterOps> EdgeFilterOps + for EdgeNotFilteredGraph +{ + #[inline] + fn edges_filtered(&self) -> bool { + true + } + + #[inline] + fn edge_list_trusted(&self) -> bool { + false + } + + #[inline] + fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { + self.graph.filter_edge(edge, layer_ids) && !self.filter.filter_edge(edge.clone(), layer_ids) + } +} diff --git a/raphtory/src/db/graph/views/filter/edge_or_filtered_graph.rs b/raphtory/src/db/graph/views/filter/edge_or_filtered_graph.rs new file mode 100644 index 0000000000..fa5e41a2df --- /dev/null +++ b/raphtory/src/db/graph/views/filter/edge_or_filtered_graph.rs @@ -0,0 +1,160 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::edges::edge_ref::EdgeStorageRef, + view::{ + internal::{ + EdgeFilterOps, EdgeHistoryFilter, Immutable, InheritCoreOps, InheritLayerOps, + InheritListOps, InheritMaterialize, InheritNodeFilterOps, + InheritNodeHistoryFilter, InheritStorageOps, InheritTimeSemantics, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalEdgeFilterOps, model::OrFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::{ + entities::{LayerIds, EID}, + storage::timeindex::TimeIndexEntry, +}; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct EdgeOrFilteredGraph { + graph: G, + left: L, + right: R, +} + +impl InternalEdgeFilterOps for OrFilter { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> + = EdgeOrFilteredGraph, R::EdgeFiltered<'graph, G>> + where + Self: 'graph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let left = self.left.create_edge_filter(graph.clone())?; + let right = self.right.create_edge_filter(graph.clone())?; + Ok(EdgeOrFilteredGraph { graph, left, right }) + } +} + +impl Base for EdgeOrFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for EdgeOrFilteredGraph {} +impl Immutable for EdgeOrFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritCoreOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritStorageOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritLayerOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritListOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritMaterialize for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritNodeFilterOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritPropertiesOps for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritTimeSemantics for EdgeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritNodeHistoryFilter + for EdgeOrFilteredGraph +{ +} + +impl EdgeHistoryFilter for EdgeOrFilteredGraph +where + L: EdgeHistoryFilter, + R: EdgeHistoryFilter, +{ + fn is_edge_prop_update_available( + &self, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_edge_prop_update_available(layer_id, prop_id, edge_id, time) + || self + .right + .is_edge_prop_update_available(layer_id, prop_id, edge_id, time) + } + + fn is_edge_prop_update_available_window( + &self, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_edge_prop_update_available_window(layer_id, prop_id, edge_id, time, w.clone()) + || self + .right + .is_edge_prop_update_available_window(layer_id, prop_id, edge_id, time, w) + } + + fn is_edge_prop_update_latest( + &self, + layer_ids: &LayerIds, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_edge_prop_update_latest(layer_ids, layer_id, prop_id, edge_id, time) + || self + .right + .is_edge_prop_update_latest(layer_ids, layer_id, prop_id, edge_id, time) + } + + fn is_edge_prop_update_latest_window( + &self, + layer_ids: &LayerIds, + layer_id: usize, + prop_id: usize, + edge_id: EID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left.is_edge_prop_update_latest_window( + layer_ids, + layer_id, + prop_id, + edge_id, + time, + w.clone(), + ) || self + .right + .is_edge_prop_update_latest_window(layer_ids, layer_id, prop_id, edge_id, time, w) + } +} + +impl EdgeFilterOps for EdgeOrFilteredGraph { + #[inline] + fn edges_filtered(&self) -> bool { + self.left.edges_filtered() && self.right.edges_filtered() + } + + #[inline] + fn edge_list_trusted(&self) -> bool { + self.left.edge_list_trusted() && self.right.edge_list_trusted() + } + + #[inline] + fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { + self.left.filter_edge(edge.clone(), layer_ids) + || self.right.filter_edge(edge.clone(), layer_ids) + } +} diff --git a/raphtory/src/db/graph/views/property_filter/edge_property_filter.rs b/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs similarity index 74% rename from raphtory/src/db/graph/views/property_filter/edge_property_filter.rs rename to raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs index 1a8c6c656f..5f5a1c51d3 100644 --- a/raphtory/src/db/graph/views/property_filter/edge_property_filter.rs +++ b/raphtory/src/db/graph/views/filter/edge_property_filtered_graph.rs @@ -3,7 +3,7 @@ use crate::{ db::{ api::{ properties::internal::InheritPropertiesOps, - storage::graph::edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, + storage::graph::edges::edge_ref::EdgeStorageRef, view::{ internal::{ EdgeFilterOps, Immutable, InheritCoreOps, InheritEdgeHistoryFilter, @@ -13,12 +13,12 @@ use crate::{ Base, }, }, - graph::{edge::EdgeView, views::property_filter::internal::InternalEdgeFilterOps}, + graph::views::filter::internal::InternalEdgeFilterOps, }, - prelude::{EdgeViewOps, GraphViewOps, PropertyFilter}, + prelude::GraphViewOps, }; -use crate::db::api::view::internal::InheritStorageOps; +use crate::db::{api::view::internal::InheritStorageOps, graph::views::filter::PropertyFilter}; #[derive(Debug, Clone)] pub struct EdgePropertyFilteredGraph { @@ -51,17 +51,14 @@ impl InternalEdgeFilterOps for PropertyFilter { self, graph: G, ) -> Result, GraphError> { - let t_prop_id = self.resolve_temporal_prop_ids(graph.edge_meta())?; - let c_prop_id = self.resolve_constant_prop_ids(graph.edge_meta())?; + let t_prop_id = self.resolve_temporal_prop_id(graph.edge_meta())?; + let c_prop_id = self.resolve_constant_prop_id(graph.edge_meta())?; Ok(EdgePropertyFilteredGraph::new( graph, t_prop_id, c_prop_id, self, )) } } -impl Static for EdgePropertyFilteredGraph {} -impl Immutable for EdgePropertyFilteredGraph {} - impl<'graph, G> Base for EdgePropertyFilteredGraph { type Base = G; @@ -70,10 +67,11 @@ impl<'graph, G> Base for EdgePropertyFilteredGraph { } } -impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for EdgePropertyFilteredGraph {} +impl Static for EdgePropertyFilteredGraph {} +impl Immutable for EdgePropertyFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for EdgePropertyFilteredGraph {} impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for EdgePropertyFilteredGraph {} - impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for EdgePropertyFilteredGraph {} impl<'graph, G: GraphViewOps<'graph>> InheritListOps for EdgePropertyFilteredGraph {} impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for EdgePropertyFilteredGraph {} @@ -84,30 +82,21 @@ impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for EdgePropertyF impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for EdgePropertyFilteredGraph {} impl<'graph, G: GraphViewOps<'graph>> EdgeFilterOps for EdgePropertyFilteredGraph { + #[inline] fn edges_filtered(&self) -> bool { true } + #[inline] fn edge_list_trusted(&self) -> bool { false } + #[inline] fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { if self.graph.filter_edge(edge, layer_ids) { - let props = EdgeView::new(&self.graph, edge.out_ref()).properties(); - let prop_value = self - .t_prop_id - .and_then(|prop_id| { - props - .temporal() - .get_by_id(prop_id) - .and_then(|prop_view| prop_view.latest()) - }) - .or_else(|| { - self.c_prop_id - .and_then(|prop_id| props.constant().get_by_id(prop_id)) - }); - self.filter.matches(prop_value.as_ref()) + self.filter + .matches_edge(&self.graph, self.t_prop_id, self.c_prop_id, edge) } else { false } 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 new file mode 100644 index 0000000000..05dd9ba347 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/exploded_edge_property_filter.rs @@ -0,0 +1,490 @@ +// use crate::{ +// core::{entities::LayerIds, utils::errors::GraphError, Prop}, +// db::{ +// api::{ +// properties::internal::InheritPropertiesOps, +// storage::graph::{ +// edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, +// nodes::node_ref::NodeStorageRef, +// }, +// view::{ +// internal::{ +// EdgeFilterOps, Immutable, InheritCoreOps, InheritEdgeHistoryFilter, +// InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, +// InheritNodeHistoryFilter, InternalLayerOps, Static, TimeSemantics, +// }, +// Base, BoxedLIter, IntoDynBoxed, +// }, +// }, +// graph::views::filter::internal::InternalExplodedEdgeFilterOps, +// }, +// prelude::GraphViewOps, +// }; +// use raphtory_api::{ +// core::{ +// entities::{edges::edge_ref::EdgeRef, VID}, +// storage::timeindex::TimeIndexEntry, +// }, +// iter::BoxedLDIter, +// }; +// use std::ops::Range; +// +// use crate::db::{ +// api::view::internal::InheritStorageOps, +// graph::views::filter::model::property_filter::PropertyFilter, +// }; +// +// #[derive(Debug, Clone)] +// pub struct ExplodedEdgePropertyFilteredGraph { +// graph: G, +// prop_id: Option, +// filter: PropertyFilter, +// } +// +// impl Static for ExplodedEdgePropertyFilteredGraph {} +// impl Immutable for ExplodedEdgePropertyFilteredGraph {} +// +// impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilteredGraph { +// pub(crate) fn new(graph: G, prop_id: Option, filter: PropertyFilter) -> Self { +// Self { +// graph, +// prop_id, +// filter, +// } +// } +// +// fn filter(&self, e: EdgeRef, t: TimeIndexEntry, layer_ids: &LayerIds) -> bool { +// self.filter.matches( +// self.prop_id +// .and_then(|prop_id| self.graph.temporal_edge_prop_at(e, prop_id, t, layer_ids)) +// .as_ref(), +// ) +// } +// } +// +// impl InternalExplodedEdgeFilterOps for PropertyFilter { +// type ExplodedEdgeFiltered<'graph, G: GraphViewOps<'graph>> = +// ExplodedEdgePropertyFilteredGraph; +// +// fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( +// self, +// graph: G, +// ) -> Result, GraphError> { +// let t_prop_id = self.resolve_temporal_prop_id(graph.edge_meta())?; +// Ok(ExplodedEdgePropertyFilteredGraph::new( +// graph.clone(), +// t_prop_id, +// self, +// )) +// } +// } +// +// impl<'graph, G> Base for ExplodedEdgePropertyFilteredGraph { +// type Base = G; +// +// fn base(&self) -> &Self::Base { +// &self.graph +// } +// } +// +// impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for ExplodedEdgePropertyFilteredGraph {} +// impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter +// for ExplodedEdgePropertyFilteredGraph +// { +// } +// impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter +// for ExplodedEdgePropertyFilteredGraph +// { +// } +// +// impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for ExplodedEdgePropertyFilteredGraph {} +// impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for ExplodedEdgePropertyFilteredGraph {} +// impl<'graph, G: GraphViewOps<'graph>> InheritListOps for ExplodedEdgePropertyFilteredGraph {} +// impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for ExplodedEdgePropertyFilteredGraph {} +// impl<'graph, G: GraphViewOps<'graph>> InheritNodeFilterOps +// for ExplodedEdgePropertyFilteredGraph +// { +// } +// impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps +// for ExplodedEdgePropertyFilteredGraph +// { +// } +// impl<'graph, G: GraphViewOps<'graph>> EdgeFilterOps for ExplodedEdgePropertyFilteredGraph { +// fn edges_filtered(&self) -> bool { +// true +// } +// +// fn edge_list_trusted(&self) -> bool { +// false +// } +// +// fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { +// self.graph.filter_edge(edge, layer_ids) +// && self +// .edge_exploded(edge.out_ref(), layer_ids) +// .next() +// .is_some() +// } +// } +// +// impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for ExplodedEdgePropertyFilteredGraph { +// fn node_earliest_time(&self, v: VID) -> Option { +// self.graph.node_earliest_time(v) +// } +// +// fn node_latest_time(&self, v: VID) -> Option { +// self.graph.node_latest_time(v) +// } +// +// fn view_start(&self) -> Option { +// self.graph.view_start() +// } +// +// fn view_end(&self) -> Option { +// self.graph.view_end() +// } +// +// fn earliest_time_global(&self) -> Option { +// self.graph.earliest_time_global() +// } +// +// fn latest_time_global(&self) -> Option { +// self.graph.latest_time_global() +// } +// +// fn earliest_time_window(&self, start: i64, end: i64) -> Option { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.earliest_time_window(start, end) +// } +// +// fn latest_time_window(&self, start: i64, end: i64) -> Option { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.latest_time_window(start, end) +// } +// +// fn node_earliest_time_window(&self, v: VID, start: i64, end: i64) -> Option { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.node_earliest_time_window(v, start, end) +// } +// +// fn node_latest_time_window(&self, v: VID, start: i64, end: i64) -> Option { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.node_latest_time_window(v, start, end) +// } +// +// fn include_node_window(&self, v: NodeStorageRef, w: Range, layer_ids: &LayerIds) -> bool { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.include_node_window(v, w, layer_ids) +// } +// +// fn include_edge_window( +// &self, +// edge: EdgeStorageRef, +// w: Range, +// layer_ids: &LayerIds, +// ) -> bool { +// self.edge_window_exploded(edge.out_ref(), w, layer_ids) +// .next() +// .is_some() +// } +// +// fn node_history(&self, v: VID) -> BoxedLIter<'_, TimeIndexEntry> { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.node_history(v) +// } +// +// fn node_history_window(&self, v: VID, w: Range) -> BoxedLIter<'_, TimeIndexEntry> { +// // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't +// // separate timestamps from node property updates and edge additions currently +// self.graph.node_history_window(v, w) +// } +// +// fn node_edge_history<'a>( +// &'a self, +// v: VID, +// w: Option>, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph.node_edge_history(v, w) +// } +// +// fn node_history_rows( +// &self, +// v: VID, +// w: Option>, +// ) -> BoxedLIter<(TimeIndexEntry, Vec<(usize, Prop)>)> { +// self.graph.node_history_rows(v, w) +// } +// +// fn node_property_history<'a>( +// &'a self, +// v: VID, +// w: Option>, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph.node_property_history(v, w) +// } +// +// fn edge_history<'a>( +// &'a self, +// e: EdgeRef, +// layer_ids: &'a LayerIds, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph +// .edge_history(e, layer_ids) +// .filter(move |t| self.filter(e, *t, layer_ids)) +// .into_dyn_boxed() +// } +// +// fn edge_history_window<'a>( +// &'a self, +// e: EdgeRef, +// layer_ids: &'a LayerIds, +// w: Range, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph +// .edge_history_window(e, layer_ids, w) +// .filter(move |t| self.filter(e, *t, layer_ids)) +// .into_dyn_boxed() +// } +// +// fn edge_exploded_count(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> usize { +// self.edge_exploded(edge.out_ref(), layer_ids).count() +// } +// +// fn edge_exploded_count_window( +// &self, +// edge: EdgeStorageRef, +// layer_ids: &LayerIds, +// w: Range, +// ) -> usize { +// self.edge_window_exploded(edge.out_ref(), w, layer_ids) +// .count() +// } +// +// fn edge_exploded<'a>(&'a self, e: EdgeRef, layer_ids: &'a LayerIds) -> BoxedLIter<'a, EdgeRef> { +// self.graph +// .edge_exploded(e, layer_ids) +// .filter(move |&e| { +// self.filter( +// e, +// e.time().expect("exploded edge should have timestamp"), +// layer_ids, +// ) +// }) +// .into_dyn_boxed() +// } +// +// fn edge_layers<'a>(&'a self, e: EdgeRef, layer_ids: &'a LayerIds) -> BoxedLIter<'a, EdgeRef> { +// self.graph +// .edge_layers(e, layer_ids) +// .filter(move |&e| self.edge_exploded(e, layer_ids).next().is_some()) +// .into_dyn_boxed() +// } +// +// fn edge_window_exploded<'a>( +// &'a self, +// e: EdgeRef, +// w: Range, +// layer_ids: &'a LayerIds, +// ) -> BoxedLIter<'a, EdgeRef> { +// self.graph +// .edge_window_exploded(e, w, layer_ids) +// .filter(move |&e| { +// self.filter( +// e, +// e.time().expect("exploded edge should have timestamp"), +// layer_ids, +// ) +// }) +// .into_dyn_boxed() +// } +// +// fn edge_window_layers<'a>( +// &'a self, +// e: EdgeRef, +// w: Range, +// layer_ids: &'a LayerIds, +// ) -> BoxedLIter<'a, EdgeRef> { +// self.graph +// .edge_window_layers(e, w.clone(), layer_ids) +// .filter(move |&e| { +// self.edge_window_exploded( +// e, +// w.clone(), +// &LayerIds::One(e.layer().expect("exploded edge should have layer")), +// ) +// .next() +// .is_some() +// }) +// .into_dyn_boxed() +// } +// +// fn edge_earliest_time(&self, e: EdgeRef, layer_ids: &LayerIds) -> Option { +// self.edge_exploded(e, layer_ids) +// .next() +// .map(|e| e.time_t().unwrap()) +// } +// +// fn edge_earliest_time_window( +// &self, +// e: EdgeRef, +// w: Range, +// layer_ids: &LayerIds, +// ) -> Option { +// self.edge_window_exploded(e, w, layer_ids) +// .next() +// .map(|e| e.time_t().unwrap()) +// } +// +// fn edge_latest_time(&self, e: EdgeRef, layer_ids: &LayerIds) -> Option { +// // FIXME: this is inefficient, need exploded to return something more useful +// self.edge_exploded(e, layer_ids) +// .last() +// .map(|e| e.time_t().unwrap()) +// } +// +// fn edge_latest_time_window( +// &self, +// e: EdgeRef, +// w: Range, +// layer_ids: &LayerIds, +// ) -> Option { +// // FIXME: this is inefficient, need exploded to return something more useful +// self.edge_window_exploded(e, w, layer_ids) +// .last() +// .map(|e| e.time_t().unwrap()) +// } +// +// fn edge_deletion_history<'a>( +// &'a self, +// e: EdgeRef, +// layer_ids: &'a LayerIds, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph +// .edge_deletion_history(e, layer_ids) +// .filter(move |t| self.filter(e, t.previous(), layer_ids)) +// .into_dyn_boxed() +// } +// +// fn edge_deletion_history_window<'a>( +// &'a self, +// e: EdgeRef, +// w: Range, +// layer_ids: &'a LayerIds, +// ) -> BoxedLIter<'a, TimeIndexEntry> { +// self.graph +// .edge_deletion_history_window(e, w, layer_ids) +// .filter(move |t| self.filter(e, t.previous(), layer_ids)) +// .into_dyn_boxed() +// } +// +// fn edge_is_valid(&self, e: EdgeRef, layer_ids: &LayerIds) -> bool { +// // FIXME: this is probably not correct +// self.graph.edge_is_valid(e, layer_ids) +// } +// +// fn edge_is_valid_at_end(&self, e: EdgeRef, layer_ids: &LayerIds, t: i64) -> bool { +// // FIXME: this is probably not correct +// self.graph.edge_is_valid_at_end(e, layer_ids, t) +// } +// +// fn has_temporal_prop(&self, prop_id: usize) -> bool { +// self.graph.has_temporal_prop(prop_id) +// } +// +// fn temporal_prop_vec(&self, prop_id: usize) -> Vec<(i64, Prop)> { +// self.graph.temporal_prop_vec(prop_id) +// } +// +// fn temporal_prop_iter(&self, prop_id: usize) -> BoxedLIter<(i64, Prop)> { +// self.graph.temporal_prop_iter(prop_id) +// } +// +// fn temporal_prop_iter_window( +// &self, +// prop_id: usize, +// start: i64, +// end: i64, +// ) -> BoxedLIter<(i64, Prop)> { +// self.graph.temporal_prop_iter_window(prop_id, start, end) +// } +// +// fn has_temporal_prop_window(&self, prop_id: usize, w: Range) -> bool { +// self.graph.has_temporal_prop_window(prop_id, w) +// } +// +// fn temporal_prop_vec_window(&self, prop_id: usize, start: i64, end: i64) -> Vec<(i64, Prop)> { +// self.graph.temporal_prop_vec_window(prop_id, start, end) +// } +// fn temporal_node_prop_hist(&self, v: VID, id: usize) -> BoxedLDIter<(TimeIndexEntry, Prop)> { +// // FIXME: this is wrong as we should not include filtered-out edges here +// self.graph.temporal_node_prop_hist(v, id) +// } +// fn temporal_node_prop_hist_window( +// &self, +// v: VID, +// id: usize, +// start: i64, +// end: i64, +// ) -> BoxedLDIter<(TimeIndexEntry, Prop)> { +// // FIXME: this is wrong as we should not include filtered-out edges here +// self.graph.temporal_node_prop_hist_window(v, id, start, end) +// } +// +// fn temporal_edge_prop_hist_window<'a>( +// &'a self, +// e: EdgeRef, +// id: usize, +// start: i64, +// end: i64, +// layer_ids: &LayerIds, +// ) -> BoxedLIter<'a, (TimeIndexEntry, Prop)> { +// self.graph +// .temporal_edge_prop_hist_window(e, id, start, end, layer_ids) +// .filter(move |(ti, _)| self.filter(e, *ti, self.layer_ids())) +// .into_dyn_boxed() +// } +// +// fn temporal_edge_prop_at( +// &self, +// e: EdgeRef, +// id: usize, +// t: TimeIndexEntry, +// layer_ids: &LayerIds, +// ) -> Option { +// self.graph +// .temporal_edge_prop_at(e, id, t, layer_ids) +// .filter(move |_| self.filter(e, t, layer_ids)) +// } +// +// fn temporal_edge_prop_hist<'a>( +// &'a self, +// e: EdgeRef, +// id: usize, +// layer_ids: &LayerIds, +// ) -> BoxedLIter<'a, (TimeIndexEntry, Prop)> { +// self.graph +// .temporal_edge_prop_hist(e, id, layer_ids) +// .filter(move |(ti, _)| self.filter(e, *ti, self.layer_ids())) +// .into_dyn_boxed() +// } +// +// fn constant_edge_prop(&self, e: EdgeRef, id: usize, layer_ids: &LayerIds) -> Option { +// self.graph.constant_edge_prop(e, id, layer_ids) +// } +// +// fn constant_edge_prop_window( +// &self, +// e: EdgeRef, +// id: usize, +// layer_ids: &LayerIds, +// w: Range, +// ) -> Option { +// self.graph.constant_edge_prop_window(e, id, layer_ids, w) +// } +// } diff --git a/raphtory/src/db/graph/views/filter/internal.rs b/raphtory/src/db/graph/views/filter/internal.rs new file mode 100644 index 0000000000..1d025d4377 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/internal.rs @@ -0,0 +1,36 @@ +use crate::{core::utils::errors::GraphError, prelude::GraphViewOps}; + +pub trait InternalEdgeFilterOps: Sized { + type EdgeFiltered<'graph, G>: GraphViewOps<'graph> + where + G: GraphViewOps<'graph>, + Self: 'graph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError>; +} + +// pub trait InternalExplodedEdgeFilterOps: Sized { +// type ExplodedEdgeFiltered<'graph, G: GraphViewOps<'graph>>: GraphViewOps<'graph> +// where +// Self: 'graph; +// +// fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( +// self, +// graph: G, +// ) -> Result, GraphError>; +// } + +pub trait InternalNodeFilterOps: Sized { + type NodeFiltered<'graph, G>: GraphViewOps<'graph> + where + Self: 'graph, + G: GraphViewOps<'graph>; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError>; +} diff --git a/raphtory/src/db/graph/views/filter/mod.rs b/raphtory/src/db/graph/views/filter/mod.rs new file mode 100644 index 0000000000..2dfbdeb285 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/mod.rs @@ -0,0 +1,2300 @@ +use crate::db::graph::views::filter::model::{ + edge_filter::EdgeFieldFilter, + node_filter::{NodeNameFilter, NodeTypeFilter}, + property_filter::PropertyFilter, +}; + +pub mod edge_and_filtered_graph; +pub mod edge_field_filtered_graph; +pub mod edge_not_filtered_graph; +pub mod edge_or_filtered_graph; +pub mod edge_property_filtered_graph; +pub mod exploded_edge_property_filter; +pub(crate) mod internal; +pub mod model; +pub mod node_and_filtered_graph; +pub mod node_name_filtered_graph; +pub mod node_not_filtered_graph; +pub mod node_or_filtered_graph; +pub mod node_property_filtered_graph; +pub mod node_type_filtered_graph; + +#[cfg(test)] +mod test_fluent_builder_apis { + use crate::db::graph::views::filter::model::{ + edge_filter::CompositeEdgeFilter, + node_filter::CompositeNodeFilter, + property_filter::{PropertyFilter, PropertyRef, Temporal}, + AsEdgeFilter, AsNodeFilter, ComposableFilter, EdgeFilter, EdgeFilterOps, Filter, + NodeFilter, NodeFilterBuilderOps, PropertyFilterOps, + }; + + #[test] + fn test_node_property_filter_build() { + let filter_expr = PropertyFilter::property("p").eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p".to_string()), + "raphtory", + )); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_const_property_filter_build() { + let filter_expr = PropertyFilter::property("p").constant().eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::ConstantProperty("p".to_string()), + "raphtory", + )); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_any_temporal_property_filter_build() { + let filter_expr = PropertyFilter::property("p") + .temporal() + .any() + .eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::TemporalProperty("p".to_string(), Temporal::Any), + "raphtory", + )); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_latest_temporal_property_filter_build() { + let filter_expr = PropertyFilter::property("p") + .temporal() + .latest() + .eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::TemporalProperty("p".to_string(), Temporal::Latest), + "raphtory", + )); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_name_filter_build() { + let filter_expr = NodeFilter::name().eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Node(Filter::eq("node_name", "raphtory")); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_type_filter_build() { + let filter_expr = NodeFilter::node_type().eq("raphtory"); + let node_property_filter = filter_expr.as_node_filter(); + let node_property_filter2 = CompositeNodeFilter::Node(Filter::eq("node_type", "raphtory")); + assert_eq!(node_property_filter, node_property_filter2); + } + + #[test] + fn test_node_filter_composition() { + let node_composite_filter = NodeFilter::name() + .eq("fire_nation") + .and(PropertyFilter::property("p2").constant().eq(2u64)) + .and(PropertyFilter::property("p1").eq(1u64)) + .and( + PropertyFilter::property("p3") + .temporal() + .any() + .eq(5u64) + .or(PropertyFilter::property("p4").temporal().latest().eq(7u64)), + ) + .or(NodeFilter::node_type().eq("raphtory")) + .or(PropertyFilter::property("p5").eq(9u64)) + .as_node_filter(); + + let node_composite_filter2 = CompositeNodeFilter::Or( + Box::new(CompositeNodeFilter::Or( + Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::Node(Filter::eq( + "node_name", + "fire_nation", + ))), + Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::ConstantProperty("p2".to_string()), + 2u64, + ))), + )), + Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p1".to_string()), + 1u64, + ))), + )), + 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::Node(Filter::eq( + "node_type", + "raphtory", + ))), + )), + Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p5".to_string()), + 9u64, + ))), + ); + + 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.as_edge_filter(); + 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.as_edge_filter(); + let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("dst", "raphtory")); + assert_eq!(edge_property_filter, edge_property_filter2); + } + + #[test] + fn test_edge_filter_composition() { + let edge_composite_filter = EdgeFilter::src() + .name() + .eq("fire_nation") + .and(PropertyFilter::property("p2").constant().eq(2u64)) + .and(PropertyFilter::property("p1").eq(1u64)) + .and( + PropertyFilter::property("p3") + .temporal() + .any() + .eq(5u64) + .or(PropertyFilter::property("p4").temporal().latest().eq(7u64)), + ) + .or(EdgeFilter::src().name().eq("raphtory")) + .or(PropertyFilter::property("p5").eq(9u64)) + .as_edge_filter(); + + 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::ConstantProperty("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(), Temporal::Any), + 5u64, + ))), + Box::new(CompositeEdgeFilter::Property(PropertyFilter::eq( + PropertyRef::TemporalProperty("p4".into(), Temporal::Latest), + 7u64, + ))), + )), + )), + 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); + } +} + +#[cfg(test)] +mod test_composite_filters { + use crate::{ + core::Prop, + db::graph::views::filter::model::{ + edge_filter::CompositeEdgeFilter, + node_filter::CompositeNodeFilter, + property_filter::{PropertyFilter, PropertyRef}, + Filter, + }, + prelude::IntoProp, + }; + use raphtory_api::core::storage::arc_str::ArcStr; + + #[test] + fn test_composite_node_filter() { + assert_eq!( + "p2 == 2", + CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p2".to_string()), + 2u64 + )) + .to_string() + ); + + 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))", + CompositeNodeFilter::Or(Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::And( + Box::new(CompositeNodeFilter::Node(Filter::is_not_in( + "node_type", + vec!["fire_nation".into(), "water_tribe".into()], + ))), + Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p2".to_string()), + 2u64, + ))), + )), + Box::new(CompositeNodeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p1".to_string()), + 1u64, + ))), + )), + Box::new(CompositeNodeFilter::Or( + Box::new(CompositeNodeFilter::Property(PropertyFilter::le( + PropertyRef::Property("p3".to_string()), + 5u64, + ))), + Box::new(CompositeNodeFilter::Property(PropertyFilter::is_in( + PropertyRef::Property("p4".to_string()), + vec![Prop::U64(10), Prop::U64(2)], + ))), + )), + )), + Box::new(CompositeNodeFilter::Or( + Box::new(CompositeNodeFilter::Node(Filter::eq("node_name", "pometry"))), + Box::new(CompositeNodeFilter::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)", + CompositeNodeFilter::And( + Box::from(CompositeNodeFilter::Node(Filter::fuzzy_search( + "name", "shivam", 1, true + ))), + Box::from(CompositeNodeFilter::Property(PropertyFilter::fuzzy_search( + PropertyRef::Property("nation".to_string()), + "air_nomad", + 1, + false, + ))), + ) + .to_string() + ); + } + + #[test] + fn test_composite_edge_filter() { + assert_eq!( + "p2 == 2", + CompositeEdgeFilter::Property(PropertyFilter::eq( + PropertyRef::Property("p2".to_string()), + 2u64 + )) + .to_string() + ); + + 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))", + 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] + fn test_fuzzy_search() { + let filter = Filter::fuzzy_search("name", "pomet", 2, false); + assert!(filter.matches(Some("pometry"))); + + let filter = Filter::fuzzy_search("name", "shivam_kapoor", 2, false); + assert!(filter.matches(Some("shivam_kapoor2"))); + + let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); + assert!(filter.matches(Some("shivam_kapoor2"))); + + let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); + assert!(filter.matches(Some("shivam_kapoor2"))); + + let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); + assert!(!filter.matches(Some("shivam1_kapoor2"))); + + let filter = Filter::fuzzy_search("name", "khivam sapoor", 2, false); + assert!(!filter.matches(Some("shivam1_kapoor2"))); + } + + #[test] + fn test_fuzzy_search_prefix_match() { + let filter = Filter::fuzzy_search("name", "pome", 2, false); + assert!(!filter.matches(Some("pometry"))); + + let filter = Filter::fuzzy_search("name", "pome", 2, true); + assert!(filter.matches(Some("pometry"))); + } + + #[test] + fn test_fuzzy_search_property() { + let filter = PropertyFilter::fuzzy_search( + PropertyRef::Property("prop".to_string()), + "pomet", + 2, + false, + ); + assert!(filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); + } + + #[test] + fn test_fuzzy_search_property_prefix_match() { + let filter = PropertyFilter::fuzzy_search( + PropertyRef::Property("prop".to_string()), + "pome", + 2, + false, + ); + assert!(!filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); + + let filter = PropertyFilter::fuzzy_search( + PropertyRef::Property("prop".to_string()), + "pome", + 2, + true, + ); + assert!(filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); + } + + #[test] + fn test_contains_match() { + let filter = PropertyFilter::contains(PropertyRef::Property("prop".to_string()), "shivam"); + + let res = filter.matches(Some(&Prop::Str(ArcStr::from("shivam_kapoor")))); + assert!(res); + + let res = filter.matches(None); + assert!(!res); + + let filter = PropertyFilter::contains(PropertyRef::Property("prop".to_string()), "am_ka"); + + let res = filter.matches(Some(&Prop::Str(ArcStr::from("shivam_kapoor")))); + assert!(res); + } + + #[test] + fn test_contains_not_match() { + let filter = + PropertyFilter::not_contains(PropertyRef::Property("prop".to_string()), "shivam"); + + let res = filter.matches(Some(&Prop::Str(ArcStr::from("shivam_kapoor")))); + assert!(!res); + + let res = filter.matches(None); + assert!(!res); + } + + #[test] + fn test_is_in_match() { + let filter = PropertyFilter::is_in( + PropertyRef::Property("prop".to_string()), + ["shivam".into_prop()], + ); + + let res = filter.matches(Some(&Prop::Str(ArcStr::from("shivam")))); + assert!(res); + + let res = filter.matches(None); + assert!(!res); + } + + #[test] + fn test_is_not_in_match() { + let filter = PropertyFilter::is_not_in( + PropertyRef::Property("prop".to_string()), + ["shivam".into_prop()], + ); + + let res = filter.matches(Some(&Prop::Str(ArcStr::from("shivam")))); + assert!(!res); + + let res = filter.matches(None); + assert!(!res); + } +} + +#[cfg(test)] +pub(crate) mod test_filters { + use crate::{ + core::IntoProp, + db::api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::StaticGraphViewOps, + }, + prelude::{AdditionOps, Graph, PropertyAdditionOps}, + }; + + #[cfg(test)] + mod test_property_semantics { + #[cfg(test)] + mod test_node_property_filter_semantics { + + use crate::{ + core::Prop, + db::{ + api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::StaticGraphViewOps, + }, + graph::views::filter::model::PropertyFilterOps, + }, + prelude::{AdditionOps, Graph, PropertyAdditionOps}, + }; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + use crate::{ + assert_filter_nodes_results, assert_search_nodes_results, + db::graph::views::{ + deletion_graph::PersistentGraph, filter::test_filters::filter_nodes_with, + }, + prelude::PropertyFilter, + }; + use tempfile::TempDir; + + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let nodes = [ + (6, "N1", vec![("p1", Prop::U64(2u64))]), + (7, "N1", vec![("p1", Prop::U64(1u64))]), + (6, "N2", vec![("p1", Prop::U64(1u64))]), + (7, "N2", vec![("p1", Prop::U64(2u64))]), + (8, "N3", vec![("p1", Prop::U64(1u64))]), + (9, "N4", vec![("p1", Prop::U64(1u64))]), + (5, "N5", vec![("p1", Prop::U64(1u64))]), + (6, "N5", vec![("p1", Prop::U64(2u64))]), + (5, "N6", vec![("p1", Prop::U64(1u64))]), + (6, "N6", vec![("p1", Prop::U64(1u64))]), + (3, "N7", vec![("p1", Prop::U64(1u64))]), + (5, "N7", vec![("p1", Prop::U64(1u64))]), + (3, "N8", vec![("p1", Prop::U64(1u64))]), + (4, "N8", vec![("p1", Prop::U64(2u64))]), + (2, "N9", vec![("p1", Prop::U64(2u64))]), + (2, "N10", vec![("q1", Prop::U64(0u64))]), + (2, "N10", vec![("p1", Prop::U64(3u64))]), + (2, "N11", vec![("p1", Prop::U64(3u64))]), + (2, "N11", vec![("q1", Prop::U64(0u64))]), + (2, "N12", vec![("q1", Prop::U64(0u64))]), + (3, "N12", vec![("p1", Prop::U64(3u64))]), + (2, "N13", vec![("q1", Prop::U64(0u64))]), + (3, "N13", vec![("p1", Prop::U64(3u64))]), + (2, "N14", vec![("q1", Prop::U64(0u64))]), + (2, "N15", vec![]), + ]; + + for (id, label, props) in nodes.iter() { + graph.add_node(*id, label, props.clone(), None).unwrap(); + } + + let constant_properties = [ + ("N1", [("p1", Prop::U64(1u64))]), + ("N4", [("p1", Prop::U64(2u64))]), + ("N9", [("p1", Prop::U64(1u64))]), + ("N10", [("p1", Prop::U64(1u64))]), + ("N11", [("p1", Prop::U64(1u64))]), + ("N12", [("p1", Prop::U64(1u64))]), + ("N13", [("p1", Prop::U64(1u64))]), + ("N14", [("p1", Prop::U64(1u64))]), + ("N15", [("p1", Prop::U64(1u64))]), + ]; + + for (node, props) in constant_properties.iter() { + graph + .node(node) + .unwrap() + .add_constant_properties(props.clone()) + .unwrap(); + } + + graph + } + + fn init_graph_for_secondary_indexes< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let graph: G = init_graph(graph); + let nodes = [ + (1, "N16", vec![("p1", Prop::U64(2u64))]), + (1, "N16", vec![("p1", Prop::U64(1u64))]), + (1, "N17", vec![("p1", Prop::U64(1u64))]), + (1, "N17", vec![("p1", Prop::U64(2u64))]), + ]; + + for (id, label, props) in nodes.iter() { + graph.add_node(*id, label, props.clone(), None).unwrap(); + } + + graph + } + + #[test] + fn test_constant_semantics() { + let filter = PropertyFilter::property("p1").constant().eq(1u64); + let expected_results = vec!["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_any_semantics() { + let filter = PropertyFilter::property("p1").temporal().any().eq(1u64); + let expected_results = vec!["N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_any_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").temporal().any().eq(1u64); + let expected_results = + vec!["N1", "N16", "N17", "N2", "N3", "N4", "N5", "N6", "N7", "N8"]; + assert_filter_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_temporal_latest_semantics() { + let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_latest_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); + let expected_results = vec!["N1", "N16", "N3", "N4", "N6", "N7"]; + assert_filter_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_property_semantics() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N14", "N15", "N3", "N4", "N6", "N7"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_property_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N14", "N15", "N16", "N3", "N4", "N6", "N7"]; + assert_filter_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_nodes_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_property_semantics_only_constant() { + // For this graph there won't be any temporal property index for property name "p1". + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let nodes = [(2, "N1", vec![("q1", Prop::U64(0u64))]), (2, "N2", vec![])]; + + for (id, label, props) in nodes.iter() { + graph.add_node(*id, label, props.clone(), None).unwrap(); + } + + let constant_properties = [ + ("N1", [("p1", Prop::U64(1u64))]), + ("N2", [("p1", Prop::U64(1u64))]), + ]; + + for (node, props) in constant_properties.iter() { + graph + .node(node) + .unwrap() + .add_constant_properties(props.clone()) + .unwrap(); + } + + graph + } + + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec!["N1", "N2"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_property_semantics_only_temporal() { + // For this graph there won't be any constant property index for property name "p1". + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let nodes = [ + (1, "N1", vec![("p1", Prop::U64(1u64))]), + (2, "N2", vec![("p1", Prop::U64(1u64))]), + (3, "N2", vec![("p1", Prop::U64(2u64))]), + (2, "N3", vec![("p1", Prop::U64(2u64))]), + (3, "N3", vec![("p1", Prop::U64(1u64))]), + (3, "N4", vec![]), + ]; + + for (id, label, props) in nodes.iter() { + graph.add_node(*id, label, props.clone(), None).unwrap(); + } + + graph + } + + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N1", "N3"]; + assert_filter_nodes_results!(init_graph, filter, expected_results); + assert_search_nodes_results!(init_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_edge_property_filter_semantics { + use crate::{ + assert_filter_edges_results, assert_search_edges_results, + core::Prop, + db::{ + api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::StaticGraphViewOps, + }, + graph::views::{ + filter::model::PropertyFilterOps, test_helpers::filter_edges_with, + }, + }, + prelude::{AdditionOps, Graph, PropertyAdditionOps}, + }; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_edges_with; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + use tempfile::TempDir; + + use crate::db::graph::views::filter::model::property_filter::PropertyFilter; + + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let edges = [ + (6, "N1", "N2", vec![("p1", Prop::U64(2u64))]), + (7, "N1", "N2", vec![("p1", Prop::U64(1u64))]), + (6, "N2", "N3", vec![("p1", Prop::U64(1u64))]), + (7, "N2", "N3", vec![("p1", Prop::U64(2u64))]), + (8, "N3", "N4", vec![("p1", Prop::U64(1u64))]), + (9, "N4", "N5", vec![("p1", Prop::U64(1u64))]), + (5, "N5", "N6", vec![("p1", Prop::U64(1u64))]), + (6, "N5", "N6", vec![("p1", Prop::U64(2u64))]), + (5, "N6", "N7", vec![("p1", Prop::U64(1u64))]), + (6, "N6", "N7", vec![("p1", Prop::U64(1u64))]), + (3, "N7", "N8", vec![("p1", Prop::U64(1u64))]), + (5, "N7", "N8", vec![("p1", Prop::U64(1u64))]), + (3, "N8", "N9", vec![("p1", Prop::U64(1u64))]), + (4, "N8", "N9", vec![("p1", Prop::U64(2u64))]), + (2, "N9", "N10", vec![("p1", Prop::U64(2u64))]), + (2, "N10", "N11", vec![("q1", Prop::U64(0u64))]), + (2, "N10", "N11", vec![("p1", Prop::U64(3u64))]), + (2, "N11", "N12", vec![("p1", Prop::U64(3u64))]), + (2, "N11", "N12", vec![("q1", Prop::U64(0u64))]), + (2, "N12", "N13", vec![("q1", Prop::U64(0u64))]), + (3, "N12", "N13", vec![("p1", Prop::U64(3u64))]), + (2, "N13", "N14", vec![("q1", Prop::U64(0u64))]), + (3, "N13", "N14", vec![("p1", Prop::U64(3u64))]), + (2, "N14", "N15", vec![("q1", Prop::U64(0u64))]), + (2, "N15", "N1", vec![]), + ]; + + for (time, src, dst, props) in edges { + graph.add_edge(time, src, dst, props, None).unwrap(); + } + + let constant_edges = [ + ("N1", "N2", vec![("p1", Prop::U64(1u64))]), + ("N4", "N5", vec![("p1", Prop::U64(2u64))]), + ("N9", "N10", vec![("p1", Prop::U64(1u64))]), + ("N10", "N11", vec![("p1", Prop::U64(1u64))]), + ("N11", "N12", vec![("p1", Prop::U64(1u64))]), + ("N12", "N13", vec![("p1", Prop::U64(1u64))]), + ("N13", "N14", vec![("p1", Prop::U64(1u64))]), + ("N14", "N15", vec![("p1", Prop::U64(1u64))]), + ("N15", "N1", vec![("p1", Prop::U64(1u64))]), + ]; + + for (src, dst, props) in constant_edges { + graph + .edge(src, dst) + .unwrap() + .add_constant_properties(props.clone(), None) + .unwrap(); + } + + graph + } + + fn init_graph_for_secondary_indexes< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let graph: G = init_graph(graph); + let edge_data = [ + (1, "N16", "N15", vec![("p1", Prop::U64(2u64))]), + (1, "N16", "N15", vec![("p1", Prop::U64(1u64))]), + (1, "N17", "N16", vec![("p1", Prop::U64(1u64))]), + (1, "N17", "N16", vec![("p1", Prop::U64(2u64))]), + ]; + + for (time, src, dst, props) in edge_data { + graph.add_edge(time, src, dst, props, None).unwrap(); + } + + graph + } + + #[test] + fn test_constant_semantics() { + let filter = PropertyFilter::property("p1").constant().eq(1u64); + let expected_results = vec![ + "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", + "N15->N1", "N9->N10", + ]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_any_semantics() { + let filter = PropertyFilter::property("p1").temporal().any().eq(1u64); + let expected_results = vec![ + "N1->N2", "N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", "N8->N9", + ]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_any_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").temporal().any().lt(2u64); + let expected_results = vec![ + "N1->N2", "N16->N15", "N17->N16", "N2->N3", "N3->N4", "N4->N5", "N5->N6", + "N6->N7", "N7->N8", "N8->N9", + ]; + assert_filter_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_temporal_latest_semantics() { + let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_temporal_latest_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); + let expected_results = + vec!["N1->N2", "N16->N15", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_property_semantics() { + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec![ + "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N8->N9", + "N9->N10", + ]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_property_semantics_for_secondary_indexes() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec![ + "N1->N2", "N14->N15", "N15->N1", "N16->N15", "N3->N4", "N4->N5", "N6->N7", + "N7->N8", + ]; + assert_filter_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + assert_search_edges_results!( + init_graph_for_secondary_indexes, + filter, + expected_results + ); + } + + #[test] + fn test_property_semantics_only_constant() { + // For this graph there won't be any temporal property index for property name "p1". + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let edges = [ + (2, "N1", "N2", vec![("q1", Prop::U64(0u64))]), + (2, "N2", "N3", vec![]), + ]; + + for (time, src, dst, props) in edges { + graph.add_edge(time, src, dst, props, None).unwrap(); + } + + let constant_edges = [ + ("N1", "N2", vec![("p1", Prop::U64(1u64))]), + ("N2", "N3", vec![("p1", Prop::U64(1u64))]), + ]; + + for (src, dst, props) in constant_edges { + graph + .edge(src, dst) + .unwrap() + .add_constant_properties(props.clone(), None) + .unwrap(); + } + + graph + } + + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N2->N3"]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + + #[test] + fn test_property_semantics_only_temporal() { + // For this graph there won't be any constant property index for property name "p1". + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let edges = [ + (1, "N1", "N2", vec![("p1", Prop::U64(1u64))]), + (2, "N2", "N3", vec![("p1", Prop::U64(1u64))]), + (3, "N2", "N3", vec![("p1", Prop::U64(2u64))]), + (2, "N3", "N4", vec![("p1", Prop::U64(2u64))]), + (3, "N3", "N4", vec![("p1", Prop::U64(1u64))]), + (2, "N4", "N5", vec![]), + ]; + + for (time, src, dst, props) in edges { + graph.add_edge(time, src, dst, props, None).unwrap(); + } + + graph + } + + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4"]; + assert_filter_edges_results!(init_graph, filter, expected_results); + assert_search_edges_results!(init_graph, filter, expected_results); + } + } + } + + use crate::db::graph::views::{ + filter::internal::{InternalEdgeFilterOps, InternalNodeFilterOps}, + test_helpers::{filter_edges_with, filter_nodes_with}, + }; + + fn init_nodes_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let nodes = [ + ( + 1, + 1, + vec![ + ("p1", "shivam_kapoor".into_prop()), + ("p9", 5u64.into_prop()), + ("p10", "Paper_airplane".into_prop()), + ], + Some("fire_nation"), + ), + ( + 2, + 2, + vec![ + ("p1", "prop12".into_prop()), + ("p2", 2u64.into_prop()), + ("p10", "Paper_ship".into_prop()), + ], + Some("air_nomads"), + ), + ( + 3, + 1, + vec![ + ("p1", "shivam_kapoor".into_prop()), + ("p9", 5u64.into_prop()), + ], + Some("fire_nation"), + ), + ( + 3, + 3, + vec![ + ("p2", 6u64.into_prop()), + ("p3", 1u64.into_prop()), + ("p10", "Paper_airplane".into_prop()), + ], + Some("fire_nation"), + ), + ( + 4, + 1, + vec![ + ("p1", "shivam_kapoor".into_prop()), + ("p9", 5u64.into_prop()), + ], + Some("fire_nation"), + ), + (3, 4, vec![("p4", "pometry".into_prop())], None), + (4, 4, vec![("p5", 12u64.into_prop())], None), + ]; + + for (time, id, props, node_type) in nodes { + graph.add_node(time, id, props, node_type).unwrap(); + } + + graph + } + + fn init_edges_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let edges = [ + ( + 1, + "1", + "2", + vec![ + ("p1", "shivam_kapoor".into_prop()), + ("p10", "Paper_airplane".into_prop()), + ], + Some("fire_nation"), + ), + ( + 2, + "1", + "2", + vec![ + ("p1", "shivam_kapoor".into_prop()), + ("p2", 4u64.into_prop()), + ], + Some("fire_nation"), + ), + ( + 2, + "2", + "3", + vec![ + ("p1", "prop12".into_prop()), + ("p2", 2u64.into_prop()), + ("p10", "Paper_ship".into_prop()), + ], + Some("air_nomads"), + ), + ( + 3, + "3", + "1", + vec![("p2", 6u64.into_prop()), ("p3", 1u64.into_prop())], + Some("fire_nation"), + ), + ( + 3, + "2", + "1", + vec![ + ("p2", 6u64.into_prop()), + ("p3", 1u64.into_prop()), + ("p10", "Paper_airplane".into_prop()), + ], + None, + ), + ( + 4, + "David Gilmour", + "John Mayer", + vec![("p2", 6u64.into_prop()), ("p3", 1u64.into_prop())], + None, + ), + ( + 4, + "John Mayer", + "Jimmy Page", + vec![("p2", 6u64.into_prop()), ("p3", 1u64.into_prop())], + None, + ), + ]; + + for (time, src, dst, props, edge_type) in edges { + graph.add_edge(time, src, dst, props, edge_type).unwrap(); + } + + graph + } + + #[cfg(test)] + mod test_node_property_filter { + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + use crate::{ + core::Prop, + db::graph::views::filter::{model::PropertyFilterOps, test_filters::init_nodes_graph}, + prelude::Graph, + }; + use tempfile::TempDir; + + use crate::{ + assert_filter_nodes_results, assert_search_nodes_results, + db::graph::views::{ + deletion_graph::PersistentGraph, + filter::model::{property_filter::PropertyFilter, ComposableFilter, NotFilter}, + test_helpers::filter_nodes_with, + }, + prelude::NodeViewOps, + }; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + #[test] + fn test_exact_match() { + let filter = PropertyFilter::property("p10").eq("Paper_airplane"); + let expected_results = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10").eq(""); + let expected_results = Vec::::new(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_not_exact_match() { + let filter = PropertyFilter::property("p10").eq("Paper"); + let expected_results: Vec<&str> = vec![]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_eq() { + let filter = PropertyFilter::property("p2").eq(2u64); + let expected_results = vec!["2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_ne() { + let filter = PropertyFilter::property("p2").ne(2u64); + let expected_results = vec!["3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_lt() { + let filter = PropertyFilter::property("p2").lt(10u64); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_le() { + let filter = PropertyFilter::property("p2").le(6u64); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_gt() { + let filter = PropertyFilter::property("p2").gt(2u64); + let expected_results = vec!["3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_ge() { + let filter = PropertyFilter::property("p2").ge(2u64); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_in() { + let filter = PropertyFilter::property("p2").is_in(vec![Prop::U64(6)]); + let expected_results = vec!["3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2").is_in(vec![Prop::U64(2), Prop::U64(6)]); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_not_in() { + let filter = PropertyFilter::property("p2").is_not_in(vec![Prop::U64(6)]); + let expected_results = vec!["2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_is_some() { + let filter = PropertyFilter::property("p2").is_some(); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_is_none() { + let filter = PropertyFilter::property("p2").is_none(); + let expected_results = vec!["1", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_contains() { + let filter = PropertyFilter::property("p10").contains("Paper"); + let expected_results: Vec<&str> = vec!["1", "2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .any() + .contains("Paper"); + let expected_results: Vec<&str> = vec!["1", "2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .latest() + .contains("Paper"); + let expected_results: Vec<&str> = vec!["1", "2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_property_contains_not() { + let filter = PropertyFilter::property("p10").not_contains("ship"); + let expected_results: Vec<&str> = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .any() + .not_contains("ship"); + let expected_results: Vec<&str> = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .latest() + .not_contains("ship"); + let expected_results: Vec<&str> = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_not_property() { + let filter = NotFilter(PropertyFilter::property("p10").contains("Paper")); + let expected_results: Vec<&str> = vec!["4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10").contains("Paper").not(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_edge_property_filter { + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + use crate::{ + assert_filter_edges_results, assert_search_edges_results, + core::Prop, + db::graph::views::filter::{ + model::PropertyFilterOps, + test_filters::{filter_edges_with, init_edges_graph}, + }, + prelude::Graph, + }; + use tempfile::TempDir; + + use crate::db::graph::views::filter::model::{ + property_filter::PropertyFilter, ComposableFilter, + }; + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_edges_with; + + #[test] + fn test_filter_edges_for_property_eq() { + let filter = PropertyFilter::property("p2").eq(2u64); + let expected_results = vec!["2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_ne() { + let filter = PropertyFilter::property("p2").ne(2u64); + let expected_results = vec![ + "1->2", + "2->1", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_lt() { + let filter = PropertyFilter::property("p2").lt(10u64); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_le() { + let filter = PropertyFilter::property("p2").le(6u64); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_gt() { + let filter = PropertyFilter::property("p2").gt(2u64); + let expected_results = vec![ + "1->2", + "2->1", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_ge() { + let filter = PropertyFilter::property("p2").ge(2u64); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_in() { + let filter = PropertyFilter::property("p2").is_in(vec![Prop::U64(6)]); + let expected_results = vec![ + "2->1", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2").is_in(vec![Prop::U64(2), Prop::U64(6)]); + let expected_results = vec![ + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_not_in() { + let filter = PropertyFilter::property("p2").is_not_in(vec![Prop::U64(6)]); + let expected_results = vec!["1->2", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_is_some() { + let filter = PropertyFilter::property("p2").is_some(); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_is_none() { + let filter = PropertyFilter::property("p2").is_none(); + let expected_results = Vec::::new(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_contains() { + let filter = PropertyFilter::property("p10").contains("Paper"); + let expected_results: Vec<&str> = vec!["1->2", "2->1", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .any() + .contains("Paper"); + let expected_results: Vec<&str> = vec!["1->2", "2->1", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .latest() + .contains("Paper"); + let expected_results: Vec<&str> = vec!["1->2", "2->1", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_property_contains_not() { + let filter = PropertyFilter::property("p10").not_contains("ship"); + let expected_results: Vec<&str> = vec!["1->2", "2->1"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .any() + .not_contains("ship"); + let expected_results: Vec<&str> = vec!["1->2", "2->1"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p10") + .temporal() + .latest() + .not_contains("ship"); + let expected_results: Vec<&str> = vec!["1->2", "2->1"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_by_fuzzy_search() { + let filter = PropertyFilter::property("p1").fuzzy_search("shiv", 2, true); + let expected_results: Vec<&str> = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p1").fuzzy_search("ShiV", 2, true); + let expected_results: Vec<&str> = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p1").fuzzy_search("shiv", 2, false); + let expected_results: Vec<&str> = vec![]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_not_property() { + let filter = PropertyFilter::property("p2").ne(2u64).not(); + let expected_results = vec!["2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_node_filter { + use crate::{ + db::graph::views::filter::test_filters::{filter_nodes_with, init_nodes_graph}, + prelude::Graph, + }; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + use crate::{ + assert_filter_nodes_results, assert_search_nodes_results, + db::graph::views::{ + deletion_graph::PersistentGraph, + filter::model::{ComposableFilter, NodeFilter, NodeFilterBuilderOps}, + }, + }; + use tempfile::TempDir; + + #[test] + fn test_filter_nodes_for_node_name_eq() { + let filter = NodeFilter::name().eq("3"); + let expected_results = vec!["3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_name_ne() { + let filter = NodeFilter::name().ne("2"); + let expected_results = vec!["1", "3", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_name_in() { + let filter = NodeFilter::name().is_in(vec!["1".into()]); + let expected_results = vec!["1"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name().is_in(vec!["".into()]); + let expected_results = Vec::<&str>::new(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name().is_in(vec!["2".into(), "3".into()]); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_name_not_in() { + let filter = NodeFilter::name().is_not_in(vec!["1".into()]); + let expected_results = vec!["2", "3", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name().is_not_in(vec!["".into()]); + let expected_results = vec!["1", "2", "3", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_eq() { + let filter = NodeFilter::node_type().eq("fire_nation"); + let expected_results = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_ne() { + let filter = NodeFilter::node_type().ne("fire_nation"); + let expected_results = vec!["2", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_in() { + let filter = NodeFilter::node_type().is_in(vec!["fire_nation".into()]); + let expected_results = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = + NodeFilter::node_type().is_in(vec!["fire_nation".into(), "air_nomads".into()]); + let expected_results = vec!["1", "2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_not_in() { + let filter = NodeFilter::node_type().is_not_in(vec!["fire_nation".into()]); + let expected_results = vec!["2", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_contains() { + let filter = NodeFilter::node_type().contains("fire"); + let expected_results = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_node_type_contains_not() { + let filter = NodeFilter::node_type().not_contains("fire"); + let expected_results = vec!["2", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_fuzzy_search() { + let filter = NodeFilter::node_type().fuzzy_search("fire", 2, true); + let expected_results: Vec<&str> = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::node_type().fuzzy_search("fire", 2, false); + let expected_results: Vec<&str> = vec![]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::node_type().fuzzy_search("air_noma", 2, false); + let expected_results: Vec<&str> = vec!["2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_filter_nodes_for_not_node_type() { + let filter = NodeFilter::node_type() + .is_not_in(vec!["fire_nation".into()]) + .not(); + let expected_results = vec!["1", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_node_composite_filter { + use crate::{db::graph::views::filter::test_filters::init_nodes_graph, prelude::Graph}; + + use crate::db::graph::views::{ + filter::model::{ + property_filter::PropertyFilter, AsNodeFilter, ComposableFilter, NodeFilter, + NodeFilterBuilderOps, PropertyFilterOps, + }, + test_helpers::filter_nodes_with, + }; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + use crate::{ + assert_filter_nodes_results, assert_search_nodes_results, + db::graph::views::deletion_graph::PersistentGraph, + }; + use tempfile::TempDir; + + #[test] + fn test_filter_nodes_by_props_added_at_different_times() { + let filter = PropertyFilter::property("p4") + .eq("pometry") + .and(PropertyFilter::property("p5").eq(12u64)); + let expected_results = vec!["4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_unique_results_from_composite_filters() { + let filter = PropertyFilter::property("p2") + .ge(2u64) + .and(PropertyFilter::property("p2").ge(1u64)); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2") + .ge(2u64) + .or(PropertyFilter::property("p2").ge(5u64)); + let expected_results = vec!["2", "3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_composite_filter_nodes() { + let filter = PropertyFilter::property("p2") + .eq(2u64) + .and(PropertyFilter::property("p1").eq("kapoor")); + let expected_results = Vec::::new(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2") + .eq(2u64) + .or(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1", "2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p1") + .eq("pometry") + .or(PropertyFilter::property("p2") + .eq(6u64) + .and(PropertyFilter::property("p3").eq(1u64))); + let expected_results = vec!["3"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::node_type() + .eq("fire_nation") + .and(PropertyFilter::property("p1").eq("prop1")); + let expected_results = Vec::::new(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = PropertyFilter::property("p9") + .eq(5u64) + .and(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::node_type() + .eq("fire_nation") + .and(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name() + .eq("2") + .and(PropertyFilter::property("p2").eq(2u64)); + let expected_results = vec!["2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name() + .eq("2") + .and(PropertyFilter::property("p2").eq(2u64)) + .or(PropertyFilter::property("p9").eq(5u64)); + let expected_results = vec!["1", "2"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + let filter = filter.as_node_filter(); + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + + #[test] + fn test_not_composite_filter_nodes() { + let filter = NodeFilter::name() + .eq("2") + .and(PropertyFilter::property("p2").eq(2u64)) + .or(PropertyFilter::property("p9").eq(5u64)) + .not(); + let expected_results = vec!["3", "4"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + + let filter = NodeFilter::name() + .eq("2") + .not() + .and(PropertyFilter::property("p2").eq(2u64)) + .or(PropertyFilter::property("p9").eq(5u64)); + let expected_results = vec!["1"]; + assert_filter_nodes_results!(init_nodes_graph, filter, expected_results); + assert_search_nodes_results!(init_nodes_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_edge_filter { + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_edges_with; + use crate::{ + assert_filter_edges_results, assert_search_edges_results, + db::graph::views::{ + filter::test_filters::init_edges_graph, test_helpers::filter_edges_with, + }, + prelude::Graph, + }; + + use crate::db::graph::views::filter::model::{ComposableFilter, EdgeFilter, EdgeFilterOps}; + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + #[test] + fn test_filter_edges_for_src_eq() { + let filter = EdgeFilter::src().name().eq("3"); + let expected_results = vec!["3->1"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_src_ne() { + let filter = EdgeFilter::src().name().ne("1"); + let expected_results = vec![ + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_src_in() { + let filter = EdgeFilter::src().name().is_in(vec!["1".into()]); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src().name().is_in(vec!["1".into(), "2".into()]); + let expected_results = vec!["1->2", "2->1", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_src_not_in() { + let filter = EdgeFilter::src().name().is_not_in(vec!["1".into()]); + let expected_results = vec![ + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_dst_eq() { + let filter = EdgeFilter::dst().name().eq("2"); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_dst_ne() { + let filter = EdgeFilter::dst().name().ne("2"); + let expected_results = vec![ + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_dst_in() { + let filter = EdgeFilter::dst().name().is_in(vec!["2".into()]); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::dst().name().is_in(vec!["2".into(), "3".into()]); + let expected_results = vec!["1->2", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_dst_not_in() { + let filter = EdgeFilter::dst().name().is_not_in(vec!["1".into()]); + let expected_results = vec![ + "1->2", + "2->3", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_src_contains() { + let filter = EdgeFilter::src().name().contains("Mayer"); + let expected_results: Vec<&str> = vec!["John Mayer->Jimmy Page"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + // assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_src_contains_not() { + let filter = EdgeFilter::src().name().not_contains("Mayer"); + let expected_results: Vec<&str> = + vec!["1->2", "2->1", "2->3", "3->1", "David Gilmour->John Mayer"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + // assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_fuzzy_search() { + let filter = EdgeFilter::src().name().fuzzy_search("John", 2, true); + let expected_results: Vec<&str> = vec!["John Mayer->Jimmy Page"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src().name().fuzzy_search("John", 2, false); + let expected_results: Vec<&str> = vec![]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src().name().fuzzy_search("John May", 2, false); + let expected_results: Vec<&str> = vec!["John Mayer->Jimmy Page"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_filter_edges_for_not_src() { + let filter = EdgeFilter::src().name().is_not_in(vec!["1".into()]).not(); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + } + + #[cfg(test)] + mod test_edge_composite_filter { + use crate::{ + assert_filter_edges_results, assert_search_edges_results, + db::graph::views::{ + filter::{test_filters::init_edges_graph, EdgeFieldFilter}, + test_helpers::filter_edges_with, + }, + prelude::Graph, + }; + use tempfile::TempDir; + + use crate::db::graph::views::filter::model::{ + property_filter::PropertyFilter, AndFilter, AsEdgeFilter, ComposableFilter, EdgeFilter, + EdgeFilterOps, PropertyFilterOps, + }; + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_edges_with; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + #[test] + fn test_filter_edge_for_src_dst() { + 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, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_unique_results_from_composite_filters() { + let filter = PropertyFilter::property("p2") + .ge(2u64) + .and(PropertyFilter::property("p2").ge(1u64)); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2") + .ge(2u64) + .or(PropertyFilter::property("p2").ge(5u64)); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_composite_filter_edges() { + let filter = PropertyFilter::property("p2") + .eq(2u64) + .and(PropertyFilter::property("p1").eq("kapoor")); + let expected_results = Vec::::new(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2") + .eq(2u64) + .or(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1->2", "2->3"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p1") + .eq("pometry") + .or(PropertyFilter::property("p2") + .eq(6u64) + .and(PropertyFilter::property("p3").eq(1u64))); + let expected_results = vec![ + "2->1", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src() + .name() + .eq("13") + .and(PropertyFilter::property("p1").eq("prop1")); + let expected_results = Vec::::new(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = PropertyFilter::property("p2") + .eq(4u64) + .and(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src() + .name() + .eq("1") + .and(PropertyFilter::property("p1").eq("shivam_kapoor")); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::dst() + .name() + .eq("1") + .and(PropertyFilter::property("p2").eq(6u64)); + let expected_results = vec!["2->1", "3->1"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src() + .name() + .eq("1") + .and(PropertyFilter::property("p1").eq("shivam_kapoor")) + .or(PropertyFilter::property("p3").eq(5u64)); + let expected_results = vec!["1->2"]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + let filter = filter.as_edge_filter(); + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + + #[test] + fn test_not_composite_filter_edges() { + let filter = EdgeFilter::src() + .name() + .eq("13") + .and(PropertyFilter::property("p1").eq("prop1")) + .not(); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + + let filter = EdgeFilter::src() + .name() + .eq("13") + .and(PropertyFilter::property("p1").eq("prop1").not()) + .not(); + let expected_results = vec![ + "1->2", + "2->1", + "2->3", + "3->1", + "David Gilmour->John Mayer", + "John Mayer->Jimmy Page", + ]; + assert_filter_edges_results!(init_edges_graph, filter, expected_results); + assert_search_edges_results!(init_edges_graph, filter, expected_results); + } + } +} diff --git a/raphtory/src/db/graph/views/filter/model/edge_filter.rs b/raphtory/src/db/graph/views/filter/model/edge_filter.rs new file mode 100644 index 0000000000..1de024bc44 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/edge_filter.rs @@ -0,0 +1,76 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{storage::graph::edges::edge_ref::EdgeStorageRef, view::BoxableGraphView}, + graph::views::filter::{ + internal::InternalEdgeFilterOps, + model::{property_filter::PropertyFilter, AndFilter, Filter, NotFilter, OrFilter}, + }, + }, + prelude::GraphViewOps, +}; +use std::{collections::HashMap, 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(Debug, Clone, PartialEq, Eq)] +pub enum CompositeEdgeFilter { + Edge(Filter), + Property(PropertyFilter), + And(Box, Box), + Or(Box, Box), + Not(Box), +} + +impl Display for CompositeEdgeFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CompositeEdgeFilter::Property(filter) => write!(f, "{}", filter), + CompositeEdgeFilter::Edge(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), + } + } +} + +impl InternalEdgeFilterOps for CompositeEdgeFilter { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + match self { + CompositeEdgeFilter::Edge(i) => { + Ok(Arc::new(EdgeFieldFilter(i).create_edge_filter(graph)?)) + } + CompositeEdgeFilter::Property(i) => Ok(Arc::new(i.create_edge_filter(graph)?)), + CompositeEdgeFilter::And(l, r) => Ok(Arc::new( + AndFilter { + left: l.deref().clone(), + right: r.deref().clone(), + } + .create_edge_filter(graph)?, + )), + CompositeEdgeFilter::Or(l, r) => Ok(Arc::new( + OrFilter { + left: l.deref().clone(), + right: r.deref().clone(), + } + .create_edge_filter(graph)?, + )), + CompositeEdgeFilter::Not(filter) => { + let base = filter.deref().clone(); + Ok(Arc::new(NotFilter(base).create_edge_filter(graph)?)) + } + } + } +} diff --git a/raphtory/src/db/graph/views/filter/model/filter_operator.rs b/raphtory/src/db/graph/views/filter/model/filter_operator.rs new file mode 100644 index 0000000000..5710e938d8 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/filter_operator.rs @@ -0,0 +1,172 @@ +use crate::{ + core::Prop, + db::graph::views::filter::model::{property_filter::PropertyFilterValue, FilterValue}, +}; +use std::{collections::HashSet, fmt, fmt::Display, ops::Deref}; +use strsim::levenshtein; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FilterOperator { + Eq, + Ne, + Lt, + Le, + Gt, + Ge, + In, + NotIn, + IsSome, + IsNone, + Contains, + NotContains, + FuzzySearch { + levenshtein_distance: usize, + prefix_match: bool, + }, +} + +impl Display for FilterOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let operator = match self { + FilterOperator::Eq => "==", + FilterOperator::Ne => "!=", + FilterOperator::Lt => "<", + FilterOperator::Le => "<=", + FilterOperator::Gt => ">", + FilterOperator::Ge => ">=", + FilterOperator::In => "IN", + FilterOperator::NotIn => "NOT_IN", + FilterOperator::IsSome => "IS_SOME", + FilterOperator::IsNone => "IS_NONE", + FilterOperator::Contains => "CONTAINS", + FilterOperator::NotContains => "NOT_CONTAINS", + FilterOperator::FuzzySearch { + levenshtein_distance, + prefix_match, + } => { + return write!(f, "FUZZY_SEARCH({},{})", levenshtein_distance, prefix_match); + } + }; + write!(f, "{}", operator) + } +} + +impl FilterOperator { + pub fn is_strictly_numeric_operation(&self) -> bool { + matches!( + self, + FilterOperator::Lt | FilterOperator::Le | FilterOperator::Gt | FilterOperator::Ge + ) + } + + fn operation(&self) -> impl Fn(&T, &T) -> bool + where + T: ?Sized + PartialEq + PartialOrd, + { + match self { + FilterOperator::Eq => T::eq, + FilterOperator::Ne => T::ne, + FilterOperator::Lt => T::lt, + FilterOperator::Le => T::le, + FilterOperator::Gt => T::gt, + FilterOperator::Ge => T::ge, + _ => panic!("Operation not supported for this operator"), + } + } + + pub fn fuzzy_search( + &self, + levenshtein_distance: usize, + prefix_match: bool, + ) -> impl Fn(&str, &str) -> bool { + move |left: &str, right: &str| { + let left = left.to_lowercase(); + let right = right.to_lowercase(); + let levenshtein_match = levenshtein(&left, &right) <= levenshtein_distance; + let prefix_match = prefix_match && right.starts_with(&left); + levenshtein_match || prefix_match + } + } + + fn collection_operation(&self) -> impl Fn(&HashSet, &T) -> bool + where + 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), + _ => panic!("Collection operation not supported for this operator"), + } + } + + pub fn apply_to_property(&self, left: &PropertyFilterValue, right: Option<&Prop>) -> bool { + match left { + PropertyFilterValue::None => match self { + FilterOperator::IsSome => right.is_some(), + FilterOperator::IsNone => right.is_none(), + _ => unreachable!(), + }, + PropertyFilterValue::Single(l) => match self { + FilterOperator::Eq + | FilterOperator::Ne + | FilterOperator::Lt + | FilterOperator::Le + | FilterOperator::Gt + | FilterOperator::Ge => right.map_or(false, |r| self.operation()(r, l)), + FilterOperator::Contains => right.map_or(false, |r| match (l, r) { + (Prop::Str(l), Prop::Str(r)) => r.deref().contains(l.deref()), + _ => unreachable!(), + }), + FilterOperator::NotContains => right.map_or(false, |r| match (l, r) { + (Prop::Str(l), Prop::Str(r)) => !r.deref().contains(l.deref()), + _ => unreachable!(), + }), + FilterOperator::FuzzySearch { + levenshtein_distance, + prefix_match, + } => right.map_or(false, |r| match (l, r) { + (Prop::Str(l), Prop::Str(r)) => { + let fuzzy_fn = self.fuzzy_search(*levenshtein_distance, *prefix_match); + fuzzy_fn(l, r) + } + _ => unreachable!(), + }), + _ => unreachable!(), + }, + PropertyFilterValue::Set(l) => match self { + FilterOperator::In | FilterOperator::NotIn => { + right.map_or(false, |r| self.collection_operation()(l, r)) + } + _ => unreachable!(), + }, + } + } + + pub fn apply(&self, left: &FilterValue, right: Option<&str>) -> bool { + match left { + FilterValue::Single(l) => match self { + FilterOperator::Eq | FilterOperator::Ne => match right { + Some(r) => self.operation()(r, l), + None => matches!(self, FilterOperator::Ne), + }, + FilterOperator::Contains => right.map_or(false, |r| r.contains(l)), + FilterOperator::NotContains => right.map_or(false, |r| !r.contains(l)), + FilterOperator::FuzzySearch { + levenshtein_distance, + prefix_match, + } => right.map_or(false, |r| { + let fuzzy_fn = self.fuzzy_search(*levenshtein_distance, *prefix_match); + fuzzy_fn(l, r) + }), + _ => unreachable!(), + }, + FilterValue::Set(l) => match self { + FilterOperator::In | FilterOperator::NotIn => match right { + Some(r) => self.collection_operation()(l, &r.to_string()), + None => matches!(self, FilterOperator::NotIn), + }, + _ => unreachable!(), + }, + } + } +} diff --git a/raphtory/src/db/graph/views/filter/model/mod.rs b/raphtory/src/db/graph/views/filter/model/mod.rs new file mode 100644 index 0000000000..371e5270cd --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/mod.rs @@ -0,0 +1,709 @@ +use crate::{ + core::Prop, + db::{ + api::storage::graph::{ + edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + }, + graph::views::filter::{ + internal::InternalNodeFilterOps, + model::{ + edge_filter::{CompositeEdgeFilter, EdgeFieldFilter}, + filter_operator::FilterOperator, + node_filter::{CompositeNodeFilter, NodeNameFilter, NodeTypeFilter}, + property_filter::{PropertyFilter, PropertyRef, Temporal}, + }, + }, + }, + prelude::{GraphViewOps, NodeViewOps}, +}; +use raphtory_api::core::entities::LayerIds; +use std::{collections::HashSet, fmt, fmt::Display, ops::Deref, sync::Arc}; + +pub mod edge_filter; +pub mod filter_operator; +pub mod node_filter; +pub mod property_filter; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FilterValue { + Single(String), + Set(Arc>), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Filter { + pub field_name: String, + pub field_value: FilterValue, + pub operator: FilterOperator, +} + +impl Display for Filter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.field_value { + FilterValue::Single(value) => { + write!(f, "{} {} {}", self.field_name, self.operator, value) + } + FilterValue::Set(values) => { + let mut sorted_values: Vec<_> = values.iter().collect(); + sorted_values.sort(); + let values_str = sorted_values + .iter() + .map(|v| format!("{}", v)) + .collect::>() + .join(", "); + write!(f, "{} {} [{}]", self.field_name, self.operator, values_str) + } + } + } +} + +impl Filter { + pub fn eq(field_name: impl Into, field_value: impl Into) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Single(field_value.into()), + operator: FilterOperator::Eq, + } + } + + pub fn ne(field_name: impl Into, field_value: impl Into) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Single(field_value.into()), + operator: FilterOperator::Ne, + } + } + + pub fn is_in( + field_name: impl Into, + field_values: impl IntoIterator, + ) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), + operator: FilterOperator::In, + } + } + + pub fn is_not_in( + field_name: impl Into, + field_values: impl IntoIterator, + ) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), + operator: FilterOperator::NotIn, + } + } + + pub fn contains(field_name: impl Into, field_value: impl Into) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Single(field_value.into()), + operator: FilterOperator::Contains, + } + } + + pub fn not_contains(field_name: impl Into, field_value: impl Into) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Single(field_value.into()), + operator: FilterOperator::NotContains, + } + } + + pub fn fuzzy_search( + field_name: impl Into, + field_value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> Self { + Self { + field_name: field_name.into(), + field_value: FilterValue::Single(field_value.into()), + operator: FilterOperator::FuzzySearch { + levenshtein_distance, + prefix_match, + }, + } + } + + pub fn matches(&self, node_value: Option<&str>) -> bool { + self.operator.apply(&self.field_value, node_value) + } + + pub fn matches_node<'graph, G: GraphViewOps<'graph>>( + &self, + graph: &G, + node_types_filter: &Arc<[bool]>, + layer_ids: &LayerIds, + node: NodeStorageRef, + ) -> bool { + match self.field_name.as_str() { + "node_name" => self.matches(Some(&node.id().to_str())), + "node_type" => { + node_types_filter + .get(node.node_type_id()) + .copied() + .unwrap_or(false) + && graph.filter_node(node, layer_ids) + } + _ => false, + } + } + + pub fn matches_edge<'graph, G: GraphViewOps<'graph>>( + &self, + graph: &G, + edge: EdgeStorageRef, + ) -> bool { + match self.field_name.as_str() { + "src" => self.matches(graph.node(edge.src()).map(|n| n.name()).as_deref()), + "dst" => self.matches(graph.node(edge.dst()).map(|n| n.name()).as_deref()), + _ => false, + } + } +} + +// Fluent Composite Filter Builder APIs +pub trait AsNodeFilter: Send + Sync { + fn as_node_filter(&self) -> CompositeNodeFilter; +} + +impl AsNodeFilter for Arc { + fn as_node_filter(&self) -> CompositeNodeFilter { + self.deref().as_node_filter() + } +} + +pub trait AsEdgeFilter: Send + Sync { + fn as_edge_filter(&self) -> CompositeEdgeFilter; +} + +impl AsEdgeFilter for Arc { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + self.deref().as_edge_filter() + } +} + +impl AsNodeFilter for CompositeNodeFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + self.clone() + } +} + +impl AsEdgeFilter for CompositeEdgeFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + self.clone() + } +} + +impl AsNodeFilter for PropertyFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::Property(self.clone()) + } +} + +impl AsEdgeFilter for PropertyFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + CompositeEdgeFilter::Property(self.clone()) + } +} + +impl AsNodeFilter for NodeNameFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::Node(self.0.clone()) + } +} + +impl AsNodeFilter for NodeTypeFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::Node(self.0.clone()) + } +} + +impl AsEdgeFilter for EdgeFieldFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + CompositeEdgeFilter::Edge(self.0.clone()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AndFilter { + pub(crate) left: L, + pub(crate) right: R, +} + +impl Display for AndFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({} AND {})", self.left, self.right) + } +} + +impl AsNodeFilter for AndFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::And( + Box::new(self.left.as_node_filter()), + Box::new(self.right.as_node_filter()), + ) + } +} + +impl AsEdgeFilter for AndFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + CompositeEdgeFilter::And( + Box::new(self.left.as_edge_filter()), + Box::new(self.right.as_edge_filter()), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OrFilter { + pub(crate) left: L, + pub(crate) right: R, +} + +impl Display for OrFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({} OR {})", self.left, self.right) + } +} + +impl AsNodeFilter for OrFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::Or( + Box::new(self.left.as_node_filter()), + Box::new(self.right.as_node_filter()), + ) + } +} + +impl AsEdgeFilter for OrFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + CompositeEdgeFilter::Or( + Box::new(self.left.as_edge_filter()), + Box::new(self.right.as_edge_filter()), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NotFilter(pub(crate) T); + +impl Display for NotFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "NOT({})", self.0) + } +} + +impl AsNodeFilter for NotFilter { + fn as_node_filter(&self) -> CompositeNodeFilter { + CompositeNodeFilter::Not(Box::new(self.0.as_node_filter())) + } +} + +impl AsEdgeFilter for NotFilter { + fn as_edge_filter(&self) -> CompositeEdgeFilter { + CompositeEdgeFilter::Not(Box::new(self.0.as_edge_filter())) + } +} + +pub trait ComposableFilter: Sized { + fn and(self, other: F) -> AndFilter { + AndFilter { + left: self, + right: other, + } + } + + fn or(self, other: F) -> OrFilter { + OrFilter { + left: self, + right: other, + } + } + + fn not(self) -> NotFilter { + NotFilter(self) + } +} + +impl ComposableFilter for PropertyFilter {} +impl ComposableFilter for NodeNameFilter {} +impl ComposableFilter for NodeTypeFilter {} +impl ComposableFilter for EdgeFieldFilter {} +impl ComposableFilter for AndFilter {} +impl ComposableFilter for OrFilter {} +impl ComposableFilter for NotFilter {} + +pub trait InternalPropertyFilterOps: Send + Sync { + fn property_ref(&self) -> PropertyRef; +} + +impl InternalPropertyFilterOps for Arc { + fn property_ref(&self) -> PropertyRef { + self.deref().property_ref() + } +} + +pub trait PropertyFilterOps { + 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 contains(&self, value: impl Into) -> PropertyFilter; + + fn not_contains(&self, value: impl Into) -> PropertyFilter; + + fn fuzzy_search( + &self, + prop_value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PropertyFilter; +} + +impl PropertyFilterOps for T { + fn eq(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::eq(self.property_ref(), value.into()) + } + + fn ne(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::ne(self.property_ref(), value.into()) + } + + fn le(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::le(self.property_ref(), value.into()) + } + + fn ge(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::ge(self.property_ref(), value.into()) + } + + fn lt(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::lt(self.property_ref(), value.into()) + } + + fn gt(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::gt(self.property_ref(), value.into()) + } + + fn is_in(&self, values: impl IntoIterator) -> PropertyFilter { + PropertyFilter::is_in(self.property_ref(), values.into_iter()) + } + + fn is_not_in(&self, values: impl IntoIterator) -> PropertyFilter { + PropertyFilter::is_not_in(self.property_ref(), values.into_iter()) + } + + fn is_none(&self) -> PropertyFilter { + PropertyFilter::is_none(self.property_ref()) + } + + fn is_some(&self) -> PropertyFilter { + PropertyFilter::is_some(self.property_ref()) + } + + fn contains(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::contains(self.property_ref(), value.into()) + } + + fn not_contains(&self, value: impl Into) -> PropertyFilter { + PropertyFilter::not_contains(self.property_ref(), value.into()) + } + + fn fuzzy_search( + &self, + prop_value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PropertyFilter { + PropertyFilter::fuzzy_search( + self.property_ref(), + prop_value.into(), + levenshtein_distance, + prefix_match, + ) + } +} + +#[derive(Clone)] +pub struct PropertyFilterBuilder(pub String); + +impl PropertyFilterBuilder { + pub fn constant(self) -> ConstPropertyFilterBuilder { + ConstPropertyFilterBuilder(self.0) + } + + pub fn temporal(self) -> TemporalPropertyFilterBuilder { + TemporalPropertyFilterBuilder(self.0) + } +} + +impl InternalPropertyFilterOps for PropertyFilterBuilder { + fn property_ref(&self) -> PropertyRef { + PropertyRef::Property(self.0.clone()) + } +} + +#[derive(Clone)] +pub struct ConstPropertyFilterBuilder(pub String); + +impl InternalPropertyFilterOps for ConstPropertyFilterBuilder { + fn property_ref(&self) -> PropertyRef { + PropertyRef::ConstantProperty(self.0.clone()) + } +} + +#[derive(Clone)] +pub struct AnyTemporalPropertyFilterBuilder(pub String); + +impl InternalPropertyFilterOps for AnyTemporalPropertyFilterBuilder { + fn property_ref(&self) -> PropertyRef { + PropertyRef::TemporalProperty(self.0.clone(), Temporal::Any) + } +} + +#[derive(Clone)] +pub struct LatestTemporalPropertyFilterBuilder(pub String); + +impl InternalPropertyFilterOps for LatestTemporalPropertyFilterBuilder { + fn property_ref(&self) -> PropertyRef { + PropertyRef::TemporalProperty(self.0.clone(), Temporal::Latest) + } +} + +#[derive(Clone)] +pub struct TemporalPropertyFilterBuilder(pub String); + +impl TemporalPropertyFilterBuilder { + pub fn any(self) -> AnyTemporalPropertyFilterBuilder { + AnyTemporalPropertyFilterBuilder(self.0) + } + + pub fn latest(self) -> LatestTemporalPropertyFilterBuilder { + LatestTemporalPropertyFilterBuilder(self.0) + } +} + +impl PropertyFilter { + pub fn property(name: impl AsRef) -> PropertyFilterBuilder { + PropertyFilterBuilder(name.as_ref().to_string()) + } +} + +pub trait InternalNodeFilterBuilderOps: Send + Sync { + type NodeFilterType: From + InternalNodeFilterOps + AsNodeFilter + Clone + 'static; + + 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 ne(&self, value: impl Into) -> Self::NodeFilterType { + Filter::ne(self.field_name(), value).into() + } + + fn is_in(&self, values: impl IntoIterator) -> Self::NodeFilterType { + Filter::is_in(self.field_name(), values).into() + } + + fn is_not_in(&self, values: impl IntoIterator) -> Self::NodeFilterType { + Filter::is_not_in(self.field_name(), values).into() + } + + fn contains(&self, value: impl Into) -> Self::NodeFilterType { + Filter::contains(self.field_name(), value).into() + } + + fn not_contains(&self, value: impl Into) -> Self::NodeFilterType { + Filter::not_contains(self.field_name(), value.into()).into() + } + + fn fuzzy_search( + &self, + value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> Self::NodeFilterType { + Filter::fuzzy_search(self.field_name(), value, levenshtein_distance, prefix_match).into() + } +} + +impl NodeFilterBuilderOps for T {} + +pub struct NodeNameFilterBuilder; + +impl InternalNodeFilterBuilderOps for NodeNameFilterBuilder { + type NodeFilterType = NodeNameFilter; + + fn field_name(&self) -> &'static str { + "node_name" + } +} + +pub struct NodeTypeFilterBuilder; + +impl InternalNodeFilterBuilderOps for NodeTypeFilterBuilder { + type NodeFilterType = NodeTypeFilter; + + fn field_name(&self) -> &'static str { + "node_type" + } +} + +#[derive(Clone)] +pub struct NodeFilter; + +impl NodeFilter { + pub fn name() -> NodeNameFilterBuilder { + NodeNameFilterBuilder + } + + pub fn node_type() -> NodeTypeFilterBuilder { + NodeTypeFilterBuilder + } +} + +pub trait InternalEdgeFilterBuilderOps: Send + Sync { + fn field_name(&self) -> &'static str; +} + +impl InternalEdgeFilterBuilderOps for Arc { + fn field_name(&self) -> &'static str { + self.deref().field_name() + } +} + +pub trait EdgeFilterOps { + fn eq(&self, value: impl Into) -> EdgeFieldFilter; + + fn ne(&self, value: impl Into) -> EdgeFieldFilter; + + fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; + + fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter; + + fn contains(&self, value: impl Into) -> EdgeFieldFilter; + + fn not_contains(&self, value: impl Into) -> EdgeFieldFilter; + + fn fuzzy_search( + &self, + value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> EdgeFieldFilter; +} + +impl EdgeFilterOps for T { + fn eq(&self, value: impl Into) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::eq(self.field_name(), value)) + } + + fn ne(&self, value: impl Into) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::ne(self.field_name(), value)) + } + + fn is_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::is_in(self.field_name(), values)) + } + + fn is_not_in(&self, values: impl IntoIterator) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::is_not_in(self.field_name(), values)) + } + + fn contains(&self, value: impl Into) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::contains(self.field_name(), value.into())) + } + + fn not_contains(&self, value: impl Into) -> EdgeFieldFilter { + EdgeFieldFilter(Filter::not_contains(self.field_name(), value.into())) + } + + 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, + )) + } +} + +pub struct EdgeSourceFilterBuilder; + +impl InternalEdgeFilterBuilderOps for EdgeSourceFilterBuilder { + fn field_name(&self) -> &'static str { + "src" + } +} + +pub struct EdgeDestinationFilterBuilder; + +impl InternalEdgeFilterBuilderOps for EdgeDestinationFilterBuilder { + fn field_name(&self) -> &'static str { + "dst" + } +} + +#[derive(Clone)] +pub struct EdgeFilter; + +#[derive(Clone)] +pub enum EdgeEndpointFilter { + Src, + Dst, +} + +impl EdgeEndpointFilter { + pub fn name(&self) -> Arc { + match self { + EdgeEndpointFilter::Src => Arc::new(EdgeSourceFilterBuilder), + EdgeEndpointFilter::Dst => Arc::new(EdgeDestinationFilterBuilder), + } + } +} + +impl EdgeFilter { + pub fn src() -> EdgeEndpointFilter { + EdgeEndpointFilter::Src + } + pub fn dst() -> EdgeEndpointFilter { + EdgeEndpointFilter::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 new file mode 100644 index 0000000000..f98559ddc6 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/node_filter.rs @@ -0,0 +1,102 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{storage::graph::nodes::node_ref::NodeStorageRef, view::BoxableGraphView}, + graph::views::filter::{ + internal::{InternalEdgeFilterOps, InternalNodeFilterOps}, + model::{property_filter::PropertyFilter, AndFilter, Filter, NotFilter, OrFilter}, + }, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::entities::LayerIds; +use std::{collections::HashMap, fmt, fmt::Display, ops::Deref, sync::Arc}; + +#[derive(Debug, Clone)] +pub struct NodeNameFilter(pub Filter); + +impl Display for NodeNameFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for NodeNameFilter { + fn from(filter: Filter) -> Self { + NodeNameFilter(filter) + } +} + +#[derive(Debug, Clone)] +pub struct NodeTypeFilter(pub Filter); + +impl Display for NodeTypeFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for NodeTypeFilter { + fn from(filter: Filter) -> Self { + NodeTypeFilter(filter) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CompositeNodeFilter { + Node(Filter), + Property(PropertyFilter), + And(Box, Box), + Or(Box, Box), + Not(Box), +} + +impl Display for CompositeNodeFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CompositeNodeFilter::Property(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), + CompositeNodeFilter::Not(filter) => write!(f, "NOT({})", filter), + } + } +} + +impl InternalNodeFilterOps for CompositeNodeFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> = Arc; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + match self { + CompositeNodeFilter::Node(i) => match i.field_name.as_str() { + "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::And(l, r) => Ok(Arc::new( + AndFilter { + left: l.deref().clone(), + right: r.deref().clone(), + } + .create_node_filter(graph)?, + )), + CompositeNodeFilter::Or(l, r) => Ok(Arc::new( + OrFilter { + left: l.deref().clone(), + right: r.deref().clone(), + } + .create_node_filter(graph)?, + )), + CompositeNodeFilter::Not(filter) => { + let base = filter.deref().clone(); + Ok(Arc::new(NotFilter(base).create_node_filter(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 new file mode 100644 index 0000000000..6a581e8cb6 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/model/property_filter.rs @@ -0,0 +1,304 @@ +use crate::{ + core::{sort_comparable_props, utils::errors::GraphError, Prop}, + db::{ + api::{ + properties::{internal::PropertiesOps, Properties}, + storage::graph::{ + edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, + nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + }, + view::{node::NodeViewOps, EdgeViewOps}, + }, + graph::{ + edge::EdgeView, node::NodeView, views::filter::model::filter_operator::FilterOperator, + }, + }, + prelude::GraphViewOps, +}; +use itertools::Itertools; +use raphtory_api::core::{entities::properties::props::Meta, storage::arc_str::ArcStr}; +use std::{collections::HashSet, fmt, fmt::Display, sync::Arc}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Temporal { + Any, + Latest, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PropertyRef { + Property(String), + ConstantProperty(String), + TemporalProperty(String, Temporal), +} + +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::ConstantProperty(name) => write!(f, "ConstantProperty({})", name), + PropertyRef::Property(name) => write!(f, "Property({})", name), + } + } +} + +impl PropertyRef { + pub fn name(&self) -> &str { + match self { + PropertyRef::Property(name) + | PropertyRef::ConstantProperty(name) + | PropertyRef::TemporalProperty(name, _) => name, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PropertyFilterValue { + None, + Single(Prop), + Set(Arc>), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PropertyFilter { + pub prop_ref: PropertyRef, + pub prop_value: PropertyFilterValue, + pub operator: FilterOperator, +} + +impl Display for PropertyFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let prop_ref_str = match &self.prop_ref { + PropertyRef::Property(name) => format!("{}", name), + PropertyRef::ConstantProperty(name) => format!("const({})", name), + PropertyRef::TemporalProperty(name, Temporal::Any) => format!("temporal_any({})", name), + PropertyRef::TemporalProperty(name, Temporal::Latest) => { + format!("temporal_latest({})", name) + } + }; + + match &self.prop_value { + PropertyFilterValue::None => { + write!(f, "{} {}", prop_ref_str, self.operator) + } + PropertyFilterValue::Single(value) => { + write!(f, "{} {} {}", prop_ref_str, self.operator, value) + } + PropertyFilterValue::Set(values) => { + let sorted_values = sort_comparable_props(values.iter().collect_vec()); + let values_str = sorted_values + .iter() + .map(|v| format!("{}", v)) + .collect::>() + .join(", "); + write!(f, "{} {} [{}]", prop_ref_str, self.operator, values_str) + } + } + } +} + +impl PropertyFilter { + pub fn eq(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Eq, + } + } + + pub fn ne(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Ne, + } + } + + pub fn le(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Le, + } + } + + pub fn ge(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Ge, + } + } + + pub fn lt(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Lt, + } + } + + pub fn gt(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Gt, + } + } + + pub fn is_in(prop_ref: PropertyRef, prop_values: impl IntoIterator) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), + operator: FilterOperator::In, + } + } + + pub fn is_not_in(prop_ref: PropertyRef, prop_values: impl IntoIterator) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), + operator: FilterOperator::NotIn, + } + } + + pub fn is_none(prop_ref: PropertyRef) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::None, + operator: FilterOperator::IsNone, + } + } + + pub fn is_some(prop_ref: PropertyRef) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::None, + operator: FilterOperator::IsSome, + } + } + + pub fn contains(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::Contains, + } + } + + pub fn not_contains(prop_ref: PropertyRef, prop_value: impl Into) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(prop_value.into()), + operator: FilterOperator::NotContains, + } + } + + pub fn fuzzy_search( + prop_ref: PropertyRef, + prop_value: impl Into, + levenshtein_distance: usize, + prefix_match: bool, + ) -> Self { + Self { + prop_ref, + prop_value: PropertyFilterValue::Single(Prop::Str(ArcStr::from(prop_value.into()))), + operator: FilterOperator::FuzzySearch { + levenshtein_distance, + prefix_match, + }, + } + } + + pub fn resolve_temporal_prop_id(&self, meta: &Meta) -> Result, GraphError> { + let prop_name = self.prop_ref.name(); + if let PropertyFilterValue::Single(value) = &self.prop_value { + Ok(meta + .temporal_prop_meta() + .get_and_validate(prop_name, value.dtype())?) + } else { + Ok(meta.temporal_prop_meta().get_id(prop_name)) + } + } + + pub fn resolve_constant_prop_id(&self, meta: &Meta) -> Result, GraphError> { + let prop_name = self.prop_ref.name(); + if let PropertyFilterValue::Single(value) = &self.prop_value { + Ok(meta + .const_prop_meta() + .get_and_validate(prop_name, value.dtype())?) + } else { + Ok(meta.const_prop_meta().get_id(prop_name)) + } + } + + pub fn matches(&self, other: Option<&Prop>) -> bool { + let value = &self.prop_value; + self.operator.apply_to_property(value, other) + } + + fn is_property_matched( + &self, + t_prop_id: Option, + c_prop_id: Option, + props: Properties, + ) -> bool { + match self.prop_ref { + PropertyRef::Property(_) => { + let prop_value = t_prop_id + .and_then(|prop_id| { + props + .temporal() + .get_by_id(prop_id) + .and_then(|prop_view| prop_view.latest()) + }) + .or_else(|| c_prop_id.and_then(|prop_id| props.constant().get_by_id(prop_id))); + self.matches(prop_value.as_ref()) + } + PropertyRef::ConstantProperty(_) => { + let prop_value = c_prop_id.and_then(|prop_id| props.constant().get_by_id(prop_id)); + self.matches(prop_value.as_ref()) + } + PropertyRef::TemporalProperty(_, Temporal::Any) => t_prop_id.map_or(false, |prop_id| { + props + .temporal() + .get_by_id(prop_id) + .filter(|prop_view| prop_view.values().any(|v| self.matches(Some(&v)))) + .is_some() + }), + PropertyRef::TemporalProperty(_, Temporal::Latest) => { + let prop_value = t_prop_id.and_then(|prop_id| { + props + .temporal() + .get_by_id(prop_id) + .and_then(|prop_view| prop_view.latest()) + }); + self.matches(prop_value.as_ref()) + } + } + } + + pub fn matches_node<'graph, G: GraphViewOps<'graph>>( + &self, + graph: &G, + t_prop_id: Option, + c_prop_id: Option, + node: NodeStorageRef, + ) -> bool { + let props = NodeView::new_internal(graph, node.vid()).properties(); + self.is_property_matched(t_prop_id, c_prop_id, props) + } + + pub fn matches_edge<'graph, G: GraphViewOps<'graph>>( + &self, + graph: &G, + t_prop_id: Option, + c_prop_id: Option, + edge: EdgeStorageRef, + ) -> bool { + let props = EdgeView::new(graph, edge.out_ref()).properties(); + self.is_property_matched(t_prop_id, c_prop_id, props) + } +} diff --git a/raphtory/src/db/graph/views/filter/node_and_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_and_filtered_graph.rs new file mode 100644 index 0000000000..cc94de5cc5 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_and_filtered_graph.rs @@ -0,0 +1,196 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::nodes::node_ref::NodeStorageRef, + view::{ + internal::{ + EdgeList, Immutable, InheritCoreOps, InheritEdgeFilterOps, + InheritEdgeHistoryFilter, InheritMaterialize, InheritStorageOps, + InheritTimeSemantics, InternalLayerOps, ListOps, NodeFilterOps, + NodeHistoryFilter, NodeList, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalNodeFilterOps, model::AndFilter}, + }, + prelude::{GraphViewOps, Layer}, +}; +use raphtory_api::core::{ + entities::{LayerIds, VID}, + storage::timeindex::TimeIndexEntry, +}; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct NodeAndFilteredGraph { + graph: G, + left: L, + right: R, + layer_ids: LayerIds, +} + +impl InternalNodeFilterOps for AndFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> + = NodeAndFilteredGraph, R::NodeFiltered<'graph, G>> + where + Self: 'graph; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let left = self.left.create_node_filter(graph.clone())?; + let right = self.right.create_node_filter(graph.clone())?; + let layer_ids = left.layer_ids().intersect(right.layer_ids()); + Ok(NodeAndFilteredGraph { + graph, + left, + right, + layer_ids, + }) + } +} + +impl Base for NodeAndFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for NodeAndFilteredGraph {} +impl Immutable for NodeAndFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritCoreOps for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritStorageOps for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritMaterialize for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritEdgeFilterOps for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritPropertiesOps for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritTimeSemantics for NodeAndFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritEdgeHistoryFilter + for NodeAndFilteredGraph +{ +} + +impl InternalLayerOps for NodeAndFilteredGraph +where + G: InternalLayerOps, +{ + fn layer_ids(&self) -> &LayerIds { + &self.layer_ids + } + + fn layer_ids_from_names(&self, key: Layer) -> Result { + Ok(self + .layer_ids + .intersect(&self.graph.layer_ids_from_names(key)?)) + } + + fn valid_layer_ids_from_names(&self, key: Layer) -> LayerIds { + self.layer_ids + .intersect(&self.graph.valid_layer_ids_from_names(key)) + } +} + +impl ListOps for NodeAndFilteredGraph +where + L: ListOps, + R: ListOps, +{ + fn node_list(&self) -> NodeList { + let left = self.left.node_list(); + let right = self.right.node_list(); + left.intersection(&right) + } + + fn edge_list(&self) -> EdgeList { + let left = self.left.edge_list(); + let right = self.right.edge_list(); + left.intersection(&right) + } +} + +impl NodeHistoryFilter for NodeAndFilteredGraph +where + L: NodeHistoryFilter, + R: NodeHistoryFilter, +{ + fn is_node_prop_update_available( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_node_prop_update_available(prop_id, node_id, time) + && self + .right + .is_node_prop_update_available(prop_id, node_id, time) + } + + fn is_node_prop_update_available_window( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_node_prop_update_available_window(prop_id, node_id, time, w.clone()) + && self + .right + .is_node_prop_update_available_window(prop_id, node_id, time, w) + } + + fn is_node_prop_update_latest( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + ) -> bool { + self.left.is_node_prop_update_latest(prop_id, node_id, time) + && self + .right + .is_node_prop_update_latest(prop_id, node_id, time) + } + + fn is_node_prop_update_latest_window( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_node_prop_update_latest_window(prop_id, node_id, time, w.clone()) + && self + .right + .is_node_prop_update_latest_window(prop_id, node_id, time, w) + } +} + +impl NodeFilterOps for NodeAndFilteredGraph { + #[inline] + fn nodes_filtered(&self) -> bool { + self.left.nodes_filtered() || self.right.nodes_filtered() + } + + #[inline] + fn node_list_trusted(&self) -> bool { + self.left.node_list_trusted() && self.right.node_list_trusted() + } + + #[inline] + fn edge_filter_includes_node_filter(&self) -> bool { + false + } + + #[inline] + fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + self.left.filter_node(node.clone(), layer_ids) && self.right.filter_node(node, 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 new file mode 100644 index 0000000000..4e91793ce6 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_name_filtered_graph.rs @@ -0,0 +1,91 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + view::{ + internal::{ + Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, NodeFilterOps, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalNodeFilterOps, model::Filter, NodeNameFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::entities::LayerIds; + +#[derive(Debug, Clone)] +pub struct NodeNameFilteredGraph { + graph: G, + filter: Filter, +} + +impl<'graph, G> NodeNameFilteredGraph { + pub(crate) fn new(graph: G, filter: Filter) -> Self { + Self { graph, filter } + } +} + +impl InternalNodeFilterOps for NodeNameFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> = NodeNameFilteredGraph; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + Ok(NodeNameFilteredGraph::new(graph, self.0)) + } +} + +impl<'graph, G> 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>> InheritCoreOps 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>> InheritEdgeFilterOps 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>> NodeFilterOps for NodeNameFilteredGraph { + #[inline] + fn nodes_filtered(&self) -> bool { + true + } + + #[inline] + fn node_list_trusted(&self) -> bool { + false + } + + #[inline] + fn edge_filter_includes_node_filter(&self) -> bool { + false + } + + #[inline] + fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + if self.graph.filter_node(node, layer_ids) { + self.filter.matches(Some(&node.id().to_str())) + } else { + false + } + } +} diff --git a/raphtory/src/db/graph/views/filter/node_not_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_not_filtered_graph.rs new file mode 100644 index 0000000000..fc5b93a810 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_not_filtered_graph.rs @@ -0,0 +1,85 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::nodes::node_ref::NodeStorageRef, + view::{ + internal::{ + Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, NodeFilterOps, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalNodeFilterOps, model::NotFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::entities::LayerIds; + +#[derive(Debug, Clone)] +pub struct NodeNotFilteredGraph { + graph: G, + filter: T, +} + +impl InternalNodeFilterOps for NotFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> + = NodeNotFilteredGraph> + where + Self: 'graph; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + let filter = self.0.create_node_filter(graph.clone())?; + Ok(NodeNotFilteredGraph { graph, filter }) + } +} + +impl Base for NodeNotFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for NodeNotFilteredGraph {} +impl Immutable for NodeNotFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, T> InheritCoreOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritStorageOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritLayerOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritListOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritMaterialize for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritEdgeFilterOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritPropertiesOps for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritTimeSemantics for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritNodeHistoryFilter for NodeNotFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, T> InheritEdgeHistoryFilter for NodeNotFilteredGraph {} + +impl NodeFilterOps for NodeNotFilteredGraph { + #[inline] + fn nodes_filtered(&self) -> bool { + true + } + + #[inline] + fn node_list_trusted(&self) -> bool { + false + } + + #[inline] + fn edge_filter_includes_node_filter(&self) -> bool { + false + } + + #[inline] + fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + self.graph.filter_node(node, layer_ids) && !self.filter.filter_node(node.clone(), layer_ids) + } +} diff --git a/raphtory/src/db/graph/views/filter/node_or_filtered_graph.rs b/raphtory/src/db/graph/views/filter/node_or_filtered_graph.rs new file mode 100644 index 0000000000..a02a202c93 --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_or_filtered_graph.rs @@ -0,0 +1,152 @@ +use crate::{ + core::utils::errors::GraphError, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::nodes::node_ref::NodeStorageRef, + view::{ + internal::{ + Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritStorageOps, + InheritTimeSemantics, NodeFilterOps, NodeHistoryFilter, Static, + }, + Base, + }, + }, + graph::views::filter::{internal::InternalNodeFilterOps, model::OrFilter}, + }, + prelude::GraphViewOps, +}; +use raphtory_api::core::{ + entities::{LayerIds, VID}, + storage::timeindex::TimeIndexEntry, +}; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct NodeOrFilteredGraph { + graph: G, + left: L, + right: R, +} + +impl InternalNodeFilterOps for OrFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> + = NodeOrFilteredGraph, R::NodeFiltered<'graph, G>> + where + Self: 'graph; + + fn create_node_filter<'graph, G: GraphViewOps<'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(NodeOrFilteredGraph { graph, left, right }) + } +} + +impl Base for NodeOrFilteredGraph { + type Base = G; + + fn base(&self) -> &Self::Base { + &self.graph + } +} + +impl Static for NodeOrFilteredGraph {} +impl Immutable for NodeOrFilteredGraph {} + +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritCoreOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritStorageOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritLayerOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritListOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritMaterialize for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritEdgeFilterOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritPropertiesOps for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritTimeSemantics for NodeOrFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>, L, R> InheritEdgeHistoryFilter + for NodeOrFilteredGraph +{ +} + +impl NodeHistoryFilter for NodeOrFilteredGraph +where + L: NodeHistoryFilter, + R: NodeHistoryFilter, +{ + fn is_node_prop_update_available( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + ) -> bool { + self.left + .is_node_prop_update_available(prop_id, node_id, time) + || self + .right + .is_node_prop_update_available(prop_id, node_id, time) + } + + fn is_node_prop_update_available_window( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_node_prop_update_available_window(prop_id, node_id, time, w.clone()) + || self + .right + .is_node_prop_update_available_window(prop_id, node_id, time, w) + } + + fn is_node_prop_update_latest( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + ) -> bool { + self.left.is_node_prop_update_latest(prop_id, node_id, time) + || self + .right + .is_node_prop_update_latest(prop_id, node_id, time) + } + + fn is_node_prop_update_latest_window( + &self, + prop_id: usize, + node_id: VID, + time: TimeIndexEntry, + w: Range, + ) -> bool { + self.left + .is_node_prop_update_latest_window(prop_id, node_id, time, w.clone()) + || self + .right + .is_node_prop_update_latest_window(prop_id, node_id, time, w) + } +} + +impl NodeFilterOps for NodeOrFilteredGraph { + #[inline] + fn nodes_filtered(&self) -> bool { + self.left.nodes_filtered() && self.right.nodes_filtered() + } + + #[inline] + fn node_list_trusted(&self) -> bool { + self.left.node_list_trusted() && self.right.node_list_trusted() + } + + #[inline] + fn edge_filter_includes_node_filter(&self) -> bool { + false + } + + #[inline] + fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + self.left.filter_node(node.clone(), layer_ids) || self.right.filter_node(node, layer_ids) + } +} diff --git a/raphtory/src/db/graph/views/property_filter/node_property_filter.rs b/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs similarity index 64% rename from raphtory/src/db/graph/views/property_filter/node_property_filter.rs rename to raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs index b13557374e..4f543a54d0 100644 --- a/raphtory/src/db/graph/views/property_filter/node_property_filter.rs +++ b/raphtory/src/db/graph/views/filter/node_property_filtered_graph.rs @@ -1,25 +1,25 @@ use crate::{ - core::{entities::LayerIds, utils::errors::GraphError}, + core::utils::errors::GraphError, db::{ api::{ properties::internal::InheritPropertiesOps, - storage::graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + storage::graph::nodes::node_ref::NodeStorageRef, view::{ internal::{ Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, - InheritTimeSemantics, NodeFilterOps, Static, + InheritStorageOps, InheritTimeSemantics, NodeFilterOps, Static, }, - node::NodeViewOps, Base, }, }, - graph::{node::NodeView, views::property_filter::internal::InternalNodePropertyFilterOps}, + graph::views::filter::{ + internal::InternalNodeFilterOps, model::property_filter::PropertyFilter, + }, }, - prelude::{GraphViewOps, PropertyFilter}, + prelude::GraphViewOps, }; - -use crate::db::api::view::internal::InheritStorageOps; +use raphtory_api::core::entities::LayerIds; #[derive(Debug, Clone)] pub struct NodePropertyFilteredGraph { @@ -45,24 +45,21 @@ impl<'graph, G> NodePropertyFilteredGraph { } } -impl InternalNodePropertyFilterOps for PropertyFilter { - type NodePropertyFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph; +impl InternalNodeFilterOps for PropertyFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> = NodePropertyFilteredGraph; - fn create_node_property_filter<'graph, G: GraphViewOps<'graph>>( + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, - ) -> Result, GraphError> { - let t_prop_id = self.resolve_temporal_prop_ids(graph.node_meta())?; - let c_prop_id = self.resolve_constant_prop_ids(graph.node_meta())?; + ) -> Result, GraphError> { + let t_prop_id = self.resolve_temporal_prop_id(graph.node_meta())?; + let c_prop_id = self.resolve_constant_prop_id(graph.node_meta())?; Ok(NodePropertyFilteredGraph::new( graph, t_prop_id, c_prop_id, self, )) } } -impl Static for NodePropertyFilteredGraph {} -impl Immutable for NodePropertyFilteredGraph {} - impl<'graph, G> Base for NodePropertyFilteredGraph { type Base = G; @@ -71,10 +68,11 @@ impl<'graph, G> Base for NodePropertyFilteredGraph { } } -impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for NodePropertyFilteredGraph {} +impl Static for NodePropertyFilteredGraph {} +impl Immutable for NodePropertyFilteredGraph {} +impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps 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 {} @@ -103,20 +101,8 @@ impl<'graph, G: GraphViewOps<'graph>> NodeFilterOps for NodePropertyFilteredGrap #[inline] fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { if self.graph.filter_node(node, layer_ids) { - let props = NodeView::new_internal(&self.graph, node.vid()).properties(); - let prop_value = self - .t_prop_id - .and_then(|prop_id| { - props - .temporal() - .get_by_id(prop_id) - .and_then(|prop_view| prop_view.latest()) - }) - .or_else(|| { - self.c_prop_id - .and_then(|prop_id| props.constant().get_by_id(prop_id)) - }); - self.filter.matches(prop_value.as_ref()) + self.filter + .matches_node(&self.graph, self.t_prop_id, self.c_prop_id, node) } else { false } 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 new file mode 100644 index 0000000000..b8c5f4b3af --- /dev/null +++ b/raphtory/src/db/graph/views/filter/node_type_filtered_graph.rs @@ -0,0 +1,977 @@ +use crate::{ + core::{entities::LayerIds, utils::errors::GraphError}, + db::{ + api::{ + properties::internal::InheritPropertiesOps, + storage::graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, + view::internal::{ + Base, Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, + InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, + InheritStorageOps, InheritTimeSemantics, NodeFilterOps, Static, + }, + }, + graph::views::filter::{internal::InternalNodeFilterOps, NodeTypeFilter}, + }, + prelude::GraphViewOps, +}; +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 InternalNodeFilterOps for NodeTypeFilter { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> = NodeTypeFilteredGraph; + + fn create_node_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>> InheritCoreOps 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>> InheritEdgeFilterOps 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>> NodeFilterOps for NodeTypeFilteredGraph { + #[inline] + fn nodes_filtered(&self) -> bool { + true + } + + #[inline] + fn node_list_trusted(&self) -> bool { + false + } + + #[inline] + fn edge_filter_includes_node_filter(&self) -> bool { + false + } + + #[inline] + fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { + self.node_types_filter + .get(node.node_type_id()) + .copied() + .unwrap_or(false) + && self.graph.filter_node(node, layer_ids) + } +} + +#[cfg(test)] +mod tests_node_type_filtered_subgraph { + use crate::{ + db::graph::views::filter::model::property_filter::{PropertyFilter, PropertyRef}, + prelude::*, + }; + + #[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 + .filter_nodes(PropertyFilter::eq(PropertyRef::Property("p1".into()), 1u64)) + .unwrap() + .nodes(), + vec!["C", "D"] + ); + + assert!(type_filtered_subgraph + .filter_edges(PropertyFilter::eq(PropertyRef::Property("p1".into()), 1u64)) + .unwrap() + .edges() + .is_empty()) + } + + mod test_filters_node_type_filtered_subgraph { + use crate::{ + db::api::view::StaticGraphViewOps, + prelude::{AdditionOps, NodeViewOps}, + }; + + macro_rules! assert_filter_results { + ($filter_fn:ident, $filter:expr, $node_types:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $node_types.clone()); + assert_eq!($expected_results, filter_results); + }}; + } + + macro_rules! assert_filter_results_w { + ($filter_fn:ident, $filter:expr, $node_types:expr, $window:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $window, $node_types.clone()); + assert_eq!($expected_results, filter_results); + }}; + } + + macro_rules! assert_filter_results_layers { + ($filter_fn:ident, $filter:expr, $node_types:expr, $layers:expr, $expected_results:expr) => {{ + let filter_results = + $filter_fn($filter.clone(), $node_types.clone(), $layers.clone()); + assert_eq!($expected_results, filter_results); + }}; + } + + macro_rules! assert_filter_results_layers_w { + ($filter_fn:ident, $filter:expr, $node_types:expr, $window:expr, $layers:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn( + $filter.clone(), + $node_types.clone(), + $layers.clone(), + $window, + ); + assert_eq!($expected_results, filter_results); + }}; + } + + #[cfg(feature = "search")] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $node_types:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $node_types); + assert_eq!($expected_results, search_results); + }}; + } + + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $node_types:expr, $expected_results:expr) => {}; + } + + #[cfg(feature = "search")] + macro_rules! assert_search_results_layers { + ($search_fn:ident, $filter:expr, $node_types:expr, $layers:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $node_types, $layers); + assert_eq!($expected_results, search_results); + }}; + } + + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_layers { + ($search_fn:ident, $filter:expr, $node_types:expr, $layers:expr, $expected_results:expr) => {}; + } + + #[cfg(feature = "search")] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $node_types:expr, $window:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $window, $node_types); + assert_eq!($expected_results, search_results); + }}; + } + + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $node_types:expr, $window:expr, $expected_results:expr) => {}; + } + + #[cfg(feature = "search")] + macro_rules! assert_search_results_layers_w { + ($search_fn:ident, $filter:expr, $node_types:expr, $layers:expr, $window:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $layers, $window, $node_types); + assert_eq!($expected_results, search_results); + }}; + } + + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_layers_w { + ($search_fn:ident, $filter:expr, $node_types:expr, $layers:expr, $window:expr, $expected_results:expr) => {}; + } + + 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() + } + + mod test_nodes_filters_node_type_filtered_subgraph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, filter::model::PropertyFilterOps, + }, + }, + prelude::{AdditionOps, Graph, GraphViewOps, TimeOps}, + }; + use std::ops::Range; + + use crate::db::graph::views::filter::internal::InternalNodeFilterOps; + + 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 + } + + fn filter_nodes( + filter: I, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_nodes_with(filter, graph.subgraph_node_types(node_types)) + } + + fn filter_nodes_w( + filter: I, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_nodes_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + fn filter_nodes_pg( + filter: I, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_nodes_with(filter, graph.subgraph_node_types(node_types)) + } + + fn filter_nodes_pg_w( + filter: I, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_nodes_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + #[cfg(feature = "search")] + mod search_nodes { + use crate::{ + prelude::{ + TimeOps, + }, + }; + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::filter::model::property_filter::PropertyFilter; + use crate::db::graph::views::filter::node_type_filtered_graph::tests_node_type_filtered_subgraph::test_filters_node_type_filtered_subgraph::test_nodes_filters_node_type_filtered_subgraph::{get_all_node_types, init_graph}; + use crate::db::graph::views::test_helpers::search_nodes_with; + use crate::prelude::{Graph, GraphViewOps}; + + pub fn search_nodes( + filter: PropertyFilter, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_nodes_with(filter, graph.subgraph_node_types(node_types)) + } + + pub fn search_nodes_w( + filter: PropertyFilter, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_nodes_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + pub fn search_nodes_pg( + filter: PropertyFilter, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_nodes_with(filter, graph.subgraph_node_types(node_types)) + } + + pub fn search_nodes_pg_w( + filter: PropertyFilter, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_nodes_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + } + + use crate::{db::graph::views::test_helpers::filter_nodes_with}; + #[cfg(feature = "search")] + use search_nodes::*; + use crate::db::graph::views::filter::model::property_filter::PropertyFilter; + 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; + + #[test] + fn test_nodes_filters() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes, filter, None, expected_results); + assert_search_results!(search_nodes, filter, None, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N7"]; + assert_filter_results!(filter_nodes, filter, node_types, expected_results); + assert_search_results!(search_nodes, filter, node_types, expected_results); + } + + #[test] + fn test_nodes_filters_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_results_w!(filter_nodes_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_nodes_w, filter, None, 6..9, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3"]; + assert_filter_results_w!( + filter_nodes_w, + filter, + node_types, + 6..9, + expected_results + ); + assert_search_results_w!( + search_nodes_w, + filter, + node_types, + 6..9, + expected_results + ); + } + + #[test] + fn test_nodes_filters_pg() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes_pg, filter, None, expected_results); + assert_search_results!(search_nodes_pg, filter, None, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N7"]; + // PropertyFilteringNotImplemented + // assert_filter_results!(filter_nodes_pg, filter, node_types, expected_results); + assert_search_results!(search_nodes_pg, filter, node_types, expected_results); + } + + #[test] + fn test_nodes_filters_pg_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6", "N7"]; + assert_filter_results_w!(filter_nodes_pg_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_nodes_pg_w, filter, None, 6..9, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N7"]; + // PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_nodes_pg_w, filter, node_types, 6..9, expected_results); + assert_search_results_w!( + search_nodes_pg_w, + filter, + node_types, + 6..9, + expected_results + ); + } + } + + mod test_edges_filters_node_type_filtered_subgraph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, + filter::{internal::InternalEdgeFilterOps, model::PropertyFilterOps}, + }, + }, + prelude::{AdditionOps, Graph, GraphViewOps, TimeOps, NO_PROPS}, + }; + use std::ops::Range; + + 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 + } + + fn filter_edges( + filter: I, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_edges_with(filter, graph.subgraph_node_types(node_types)) + } + + fn filter_edges_layers( + filter: I, + node_types: Option>, + layers: Vec<&str>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap(); + filter_edges_with(filter, graph) + } + + fn filter_edges_w( + filter: I, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_edges_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + fn filter_edges_layers_w( + filter: I, + node_types: Option>, + layers: Vec<&str>, + w: Range, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap() + .window(w.start, w.end); + filter_edges_with(filter, graph) + } + + #[allow(dead_code)] + fn filter_edges_pg( + filter: I, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_edges_with(filter, graph.subgraph_node_types(node_types)) + } + + #[allow(dead_code)] + fn filter_edges_pg_w( + filter: I, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + filter_edges_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + #[cfg(feature = "search")] + mod search_edges { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::filter::model::property_filter::PropertyFilter; + use crate::db::graph::views::filter::node_type_filtered_graph::tests_node_type_filtered_subgraph::test_filters_node_type_filtered_subgraph::test_edges_filters_node_type_filtered_subgraph::{get_all_node_types, init_graph}; + use crate::db::graph::views::test_helpers::search_edges_with; + use crate::prelude::{Graph, GraphViewOps, LayerOps, TimeOps}; + + pub fn search_edges( + filter: PropertyFilter, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_edges_with(filter, graph.subgraph_node_types(node_types)) + } + + pub fn search_edges_w( + filter: PropertyFilter, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_edges_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + pub fn search_edges_layers( + filter: PropertyFilter, + node_types: Option>, + layers: Vec<&str>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap(); + search_edges_with(filter, graph) + } + + pub fn search_edges_layers_w( + filter: PropertyFilter, + layers: Vec<&str>, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap() + .window(w.start, w.end); + search_edges_with(filter, graph) + } + + pub fn search_edges_pg( + filter: PropertyFilter, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_edges_with(filter, graph.subgraph_node_types(node_types)) + } + + pub fn search_edges_pg_w( + filter: PropertyFilter, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + search_edges_with( + filter, + graph.subgraph_node_types(node_types).window(w.start, w.end), + ) + } + + pub fn search_edges_pg_layers( + filter: PropertyFilter, + node_types: Option>, + layers: Vec<&str>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap(); + search_edges_with(filter, graph) + } + + pub fn search_edges_pg_layers_w( + filter: PropertyFilter, + layers: Vec<&str>, + w: Range, + node_types: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_types: Vec = + node_types.unwrap_or_else(|| get_all_node_types(&graph)); + let graph = graph + .subgraph_node_types(node_types) + .layers(layers) + .unwrap() + .window(w.start, w.end); + search_edges_with(filter, graph) + } + } + + use crate::{db::graph::views::test_helpers::filter_edges_with, prelude::LayerOps}; + #[cfg(feature = "search")] + use search_edges::*; + use crate::db::graph::views::filter::model::property_filter::PropertyFilter; + 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; + + #[test] + fn test_edges_filters() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_results!(filter_edges, filter, None, expected_results); + assert_search_results!(search_edges, filter, None, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5"]; + assert_filter_results!(filter_edges, filter, node_types.clone(), expected_results); + assert_search_results!(search_edges, filter, node_types.clone(), expected_results); + + let layers = vec!["fire_nation"]; + let expected_results = vec!["N3->N4"]; + assert_filter_results_layers!( + filter_edges_layers, + filter, + node_types, + layers, + expected_results + ); + assert_search_results_layers!( + search_edges_layers, + filter, + node_types, + layers, + expected_results + ); + } + + #[test] + fn test_edges_filters_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, None, 6..9, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4"]; + assert_filter_results_w!( + filter_edges_w, + filter, + node_types.clone(), + 6..9, + expected_results + ); + assert_search_results_w!( + search_edges_w, + filter, + node_types.clone(), + 6..9, + expected_results + ); + + let layers = vec!["fire_nation"]; + let expected_results = vec!["N3->N4"]; + assert_filter_results_layers_w!( + filter_edges_layers_w, + filter, + node_types, + 6..9, + layers, + expected_results + ); + assert_search_results_layers_w!( + search_edges_layers_w, + filter, + node_types, + layers, + 6..9, + expected_results + ); + } + + #[test] + fn test_edges_filters_pg() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + // PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, None, expected_results); + assert_search_results!(search_edges_pg, filter, None, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5"]; + // PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, node_types, expected_results); + assert_search_results!( + search_edges_pg, + filter, + node_types.clone(), + expected_results + ); + + let layers = vec!["fire_nation"]; + let expected_results = vec!["N3->N4"]; + assert_search_results_layers!( + search_edges_pg_layers, + filter, + node_types, + layers, + expected_results + ); + } + + #[test] + fn test_edges_filters_pg_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]; + // PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, None, 6..9, expected_results); + + let node_types: Option> = + Some(vec!["air_nomad".into(), "water_tribe".into()]); + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4"]; + // PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, node_types, 6..9, expected_results); + assert_search_results_w!( + search_edges_pg_w, + filter, + node_types.clone(), + 6..9, + expected_results + ); + + let layers = vec!["fire_nation"]; + let expected_results = vec!["N3->N4"]; + assert_search_results_layers_w!( + search_edges_pg_layers_w, + filter, + node_types, + layers, + 6..9, + expected_results + ); + } + } + } +} diff --git a/raphtory/src/db/graph/views/layer_graph.rs b/raphtory/src/db/graph/views/layer_graph.rs index 73a538cb61..c5201056f1 100644 --- a/raphtory/src/db/graph/views/layer_graph.rs +++ b/raphtory/src/db/graph/views/layer_graph.rs @@ -231,423 +231,569 @@ mod test_layers { }); } - #[cfg(all(test, feature = "search"))] - mod search_nodes_layer_graph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, - }, - }, - prelude::{AdditionOps, Graph, LayerOps, NodeViewOps, PropertyFilter, TimeOps}, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - let edges = vec![ - (6, "N1", "N2", vec![("p1", Prop::U64(2u64))], Some("layer1")), - (7, "N1", "N2", vec![("p1", Prop::U64(1u64))], Some("layer2")), - (6, "N2", "N3", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (7, "N2", "N3", vec![("p1", Prop::U64(2u64))], Some("layer2")), - (8, "N3", "N4", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (9, "N4", "N5", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (5, "N5", "N6", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (6, "N5", "N6", vec![("p1", Prop::U64(2u64))], Some("layer2")), - (5, "N6", "N7", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (6, "N6", "N7", vec![("p1", Prop::U64(1u64))], Some("layer2")), - (3, "N7", "N8", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (5, "N7", "N8", vec![("p1", Prop::U64(1u64))], Some("layer2")), - (3, "N8", "N1", vec![("p1", Prop::U64(1u64))], Some("layer1")), - (4, "N8", "N1", vec![("p1", Prop::U64(2u64))], Some("layer2")), - ]; - - for (id, src, tgt, props, layer) in &edges { - graph - .add_edge(*id, src, tgt, props.clone(), *layer) - .unwrap(); - } - - 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")), - ]; - - for (id, name, props, label) in &nodes { - graph.add_node(*id, name, props.clone(), *label).unwrap(); - } + mod test_filters_layer_graph { - graph + macro_rules! assert_filter_results { + ($filter_fn:ident, $filter:expr, $layers:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $layers.clone()); + assert_eq!($expected_results, filter_results); + }}; } - fn search_nodes_by_composite_filter( - graph: &G, - filter: FilterExpr, - layers: Vec, - ) -> Vec { - graph.create_index().unwrap(); - let lgv = graph - .layers(layers.clone()) - .expect("Failed to get graph for layers"); - let mut results = lgv - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results + macro_rules! assert_filter_results_w { + ($filter_fn:ident, $filter:expr, $layers:expr, $window:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $window, $layers.clone()); + assert_eq!($expected_results, filter_results); + }}; } - fn search_nodes_by_composite_filter_w( - graph: &G, - w: Range, - filter: FilterExpr, - layers: Vec, - ) -> Vec { - graph.create_index().unwrap(); - let lgv = graph - .layers(layers.clone()) - .expect("Failed to get graph for layers"); - - let mut results = lgv - .window(w.start, w.end) - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results + #[cfg(feature = "search")] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $layers:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $layers); + assert_eq!($expected_results, search_results); + }}; } - // Layers don't have any effect on the number of nodes in a graph. - // In other words, it is as good as applying no layer filters. - #[test] - fn test_search_nodes_layer_graph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $layers:expr, $expected_results:expr) => {}; } - #[test] - fn test_search_nodes_layer_graph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6"]); - - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6"]); - - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6"]); + #[cfg(feature = "search")] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $layers:expr, $window:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $window, $layers); + assert_eq!($expected_results, search_results); + }}; } - #[test] - fn test_search_nodes_persistent_layer_graph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $layers:expr, $window:expr, $expected_results:expr) => {}; } - #[test] - fn test_search_nodes_persistent_layer_graph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); - - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); - - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); + mod test_nodes_filters_layer_graph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, filter::model::PropertyFilterOps, + }, + }, + prelude::{AdditionOps, Graph, LayerOps, PropertyFilter, TimeOps}, + }; + use std::ops::Range; + + use crate::db::graph::views::{ + filter::internal::InternalNodeFilterOps, test_helpers::filter_nodes_with, + }; + + fn init_graph(graph: G) -> G { + let edges = vec![ + (6, "N1", "N2", vec![("p1", Prop::U64(2u64))], Some("layer1")), + (7, "N1", "N2", vec![("p1", Prop::U64(1u64))], Some("layer2")), + (6, "N2", "N3", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (7, "N2", "N3", vec![("p1", Prop::U64(2u64))], Some("layer2")), + (8, "N3", "N4", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (9, "N4", "N5", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (5, "N5", "N6", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (6, "N5", "N6", vec![("p1", Prop::U64(2u64))], Some("layer2")), + (5, "N6", "N7", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (6, "N6", "N7", vec![("p1", Prop::U64(1u64))], Some("layer2")), + (3, "N7", "N8", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (5, "N7", "N8", vec![("p1", Prop::U64(1u64))], Some("layer2")), + (3, "N8", "N1", vec![("p1", Prop::U64(1u64))], Some("layer1")), + (4, "N8", "N1", vec![("p1", Prop::U64(2u64))], Some("layer2")), + ]; + + for (id, src, tgt, props, layer) in &edges { + graph + .add_edge(*id, src, tgt, props.clone(), *layer) + .unwrap(); + } + + 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")), + ]; + + for (id, name, props, label) in &nodes { + graph.add_node(*id, name, props.clone(), *label).unwrap(); + } + + graph + } + + fn filter_nodes( + filter: I, + layers: Vec, + ) -> Vec { + filter_nodes_with( + filter, + init_graph(Graph::new()).layers(layers.clone()).unwrap(), + ) + } + + fn filter_nodes_w( + filter: I, + w: Range, + layers: Vec, + ) -> Vec { + filter_nodes_with( + filter, + init_graph(Graph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } + + fn filter_nodes_pg( + filter: I, + layers: Vec, + ) -> Vec { + filter_nodes_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap(), + ) + } + + fn filter_nodes_pg_w( + filter: I, + w: Range, + layers: Vec, + ) -> Vec { + filter_nodes_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } + + #[cfg(feature = "search")] + mod search_nodes { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::layer_graph::test_layers::test_filters_layer_graph::test_nodes_filters_layer_graph::init_graph; + use crate::db::graph::views::test_helpers::search_nodes_with; + use crate::prelude::{Graph, LayerOps, PropertyFilter, TimeOps}; + + pub fn search_nodes(filter: PropertyFilter, layers: Vec) -> Vec { + search_nodes_with( + filter, + init_graph(Graph::new()).layers(layers.clone()).unwrap(), + ) + } + + pub fn search_nodes_w( + filter: PropertyFilter, + w: Range, + layers: Vec, + ) -> Vec { + search_nodes_with( + filter, + init_graph(Graph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } + + pub fn search_nodes_pg(filter: PropertyFilter, layers: Vec) -> Vec { + search_nodes_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap(), + ) + } + + pub fn search_nodes_pg_w( + filter: PropertyFilter, + w: Range, + layers: Vec, + ) -> Vec { + search_nodes_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } + } + + #[cfg(feature = "search")] + use search_nodes::*; + + // Layers don't have any effect on the number of nodes in a graph. + // In other words, it is as good as applying no layer filters. + #[test] + fn test_nodes_filters() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes, filter, layers, expected_results); + assert_search_results!(search_nodes, filter, layers, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2", "N5", "N8"]; + assert_filter_results!(filter_nodes, filter, layers, expected_results); + assert_search_results!(search_nodes, filter, layers, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes, filter, layers, expected_results); + assert_search_results!(search_nodes, filter, layers, expected_results); + } + + #[test] + fn test_nodes_filters_w() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_results_w!(filter_nodes_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2", "N5"]; + assert_filter_results_w!(filter_nodes_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_results_w!(filter_nodes_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_w, filter, layers, 6..9, expected_results); + } + + #[test] + fn test_nodes_filters_pg() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes_pg, filter, layers, expected_results); + assert_search_results!(search_nodes_pg, filter, layers, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes_pg, filter, layers, expected_results); + assert_search_results!(search_nodes_pg, filter, layers, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2", "N5", "N8"]; + assert_filter_results!(filter_nodes_pg, filter, layers, expected_results); + assert_search_results!(search_nodes_pg, filter, layers, expected_results); + } + + #[test] + fn test_nodes_filters_pg_w() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6", "N7"]; + assert_filter_results_w!(filter_nodes_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_pg_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N1", "N3", "N6", "N7"]; + assert_filter_results_w!(filter_nodes_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_pg_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2", "N5", "N8"]; + assert_filter_results_w!(filter_nodes_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_nodes_pg_w, filter, layers, 6..9, expected_results); + } } - } - #[cfg(all(test, feature = "search"))] - mod search_edges_layer_graph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, + mod test_edges_filters_layer_graph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, + filter::{internal::InternalEdgeFilterOps, model::PropertyFilterOps}, + }, }, - }, - prelude::{ - AdditionOps, EdgeViewOps, Graph, LayerOps, NodeViewOps, PropertyFilter, TimeOps, - }, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - graph - .add_edge(6, "N1", "N2", [("p1", Prop::U64(2u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(7, "N1", "N2", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(6, "N2", "N3", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(7, "N2", "N3", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(8, "N3", "N4", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - - graph - .add_edge(9, "N4", "N5", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - - graph - .add_edge(5, "N5", "N6", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(6, "N5", "N6", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(5, "N6", "N7", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(6, "N6", "N7", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(3, "N7", "N8", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(5, "N7", "N8", [("p1", Prop::U64(1u64))], Some("layer2")) - .unwrap(); - - graph - .add_edge(3, "N8", "N1", [("p1", Prop::U64(1u64))], Some("layer1")) - .unwrap(); - graph - .add_edge(4, "N8", "N1", [("p1", Prop::U64(2u64))], Some("layer2")) - .unwrap(); - - graph - } + prelude::{AdditionOps, Graph, LayerOps, PropertyFilter, TimeOps}, + }; + use std::ops::Range; + + use crate::db::graph::views::test_helpers::filter_edges_with; + + fn init_graph(graph: G) -> G { + let edges = vec![ + (6, "N1", "N2", 2u64, "layer1"), + (7, "N1", "N2", 1u64, "layer2"), + (6, "N2", "N3", 1u64, "layer1"), + (7, "N2", "N3", 2u64, "layer2"), + (8, "N3", "N4", 1u64, "layer1"), + (9, "N4", "N5", 1u64, "layer1"), + (5, "N5", "N6", 1u64, "layer1"), + (6, "N5", "N6", 2u64, "layer2"), + (5, "N6", "N7", 1u64, "layer1"), + (6, "N6", "N7", 1u64, "layer2"), + (3, "N7", "N8", 1u64, "layer1"), + (5, "N7", "N8", 1u64, "layer2"), + (3, "N8", "N1", 1u64, "layer1"), + (4, "N8", "N1", 2u64, "layer2"), + ]; + + for (ts, src, dst, p1_val, layer) in edges { + graph + .add_edge(ts, src, dst, [("p1", Prop::U64(p1_val))], Some(layer)) + .unwrap(); + } - fn search_edges_by_composite_filter( - graph: &G, - filter: FilterExpr, - layers: Vec, - ) -> Vec { - graph.create_index().unwrap(); - let lgv = graph - .layers(layers.clone()) - .expect("Failed to get graph for layers"); - let mut results = lgv - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } + graph + } - fn search_edges_by_composite_filter_w( - graph: &G, - w: Range, - filter: FilterExpr, - layers: Vec, - ) -> Vec { - graph.create_index().unwrap(); - let lgv = graph - .layers(layers.clone()) - .expect("Failed to get graph for layers"); - let mut results = lgv - .window(w.start, w.end) - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } + fn filter_edges( + filter: I, + layers: Vec, + ) -> Vec { + filter_edges_with( + filter, + init_graph(Graph::new()).layers(layers.clone()).unwrap(), + ) + } - #[test] - fn test_search_edges_layer_graph() { - let graph = Graph::new(); - let graph = init_graph(graph); + fn filter_edges_w( + filter: I, + w: Range, + layers: Vec, + ) -> Vec { + filter_edges_with( + filter, + init_graph(Graph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); + #[allow(dead_code)] + fn filter_edges_pg( + filter: I, + layers: Vec, + ) -> Vec { + filter_edges_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap(), + ) + } - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!( - results, - vec!["N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", "N8->N1"] - ); + #[allow(dead_code)] + fn filter_edges_pg_w( + filter: I, + w: Range, + layers: Vec, + ) -> Vec { + filter_edges_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1->N2", "N6->N7", "N7->N8"]); - } + #[cfg(feature = "search")] + mod search_edges { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::layer_graph::test_layers::test_filters_layer_graph::test_edges_filters_layer_graph::init_graph; + use crate::db::graph::views::test_helpers::search_edges_with; + use crate::prelude::{Graph, LayerOps, PropertyFilter, TimeOps}; + + pub fn search_edges(filter: PropertyFilter, layers: Vec) -> Vec { + search_edges_with( + filter, + init_graph(Graph::new()).layers(layers.clone()).unwrap(), + ) + } - #[test] - fn test_search_edges_layer_graph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); - - // Edge Property Semantics: - // 1. All property updates to an edge belong to a layer (or _default if no layer specified) - // 2. However, when asked for a value of a particular property for an edge, the latest update - // across all specified layers (or all layers if no layers specified) is returned! - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7"]); - - // Edge Property Semantics: - // When filtering by specific layer, filter criteria (p1==1) and latest semantics is applicable - // only to that specific layer. - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N2->N3", "N3->N4"]); - - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1->N2", "N6->N7"]); - } + pub fn search_edges_w( + filter: PropertyFilter, + w: Range, + layers: Vec, + ) -> Vec { + search_edges_with( + filter, + init_graph(Graph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } - #[test] - fn test_search_edges_persistent_layer_graph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + pub fn search_edges_pg(filter: PropertyFilter, layers: Vec) -> Vec { + search_edges_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap(), + ) + } - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); + pub fn search_edges_pg_w( + filter: PropertyFilter, + w: Range, + layers: Vec, + ) -> Vec { + search_edges_with( + filter, + init_graph(PersistentGraph::new()) + .layers(layers.clone()) + .unwrap() + .window(w.start, w.end), + ) + } + } - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!( - results, - vec!["N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", "N8->N1"] - ); + #[cfg(feature = "search")] + use search_edges::*; + + #[test] + fn test_edges_filters() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_results!(filter_edges, filter, layers, expected_results); + assert_search_results!(search_edges, filter, layers, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec![ + "N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", "N8->N1", + ]; + assert_filter_results!(filter_edges, filter, layers, expected_results); + assert_search_results!(search_edges, filter, layers, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2->N3", "N5->N6", "N8->N1"]; + assert_filter_results!(filter_edges, filter, layers, expected_results); + assert_search_results!(search_edges, filter, layers, expected_results); + } - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, filter, layers); - assert_eq!(results, vec!["N1->N2", "N6->N7", "N7->N8"]); - } + #[test] + fn test_edges_filter_w() { + // Edge Property Semantics: + // 1. All property updates to an edge belong to a layer (or _default if no layer specified) + // 2. However, when asked for a value of a particular property for an edge, the latest update + // across all specified layers (or all layers if no layers specified) is returned! + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, layers, 6..9, expected_results); + + // Edge Property Semantics: + // When filtering by specific layer, filter criteria (p1==1) and latest semantics is applicable + // only to that specific layer. + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N2->N3", "N3->N4"]; + assert_filter_results_w!(filter_edges_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, layers, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_layer_graph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let layers = vec!["layer1".into(), "layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - - // Why is the edge N8 -> N1 included in the results? - // The reason edge N8 -> N1 is included as part of the results because of following two semantic reasons: - // .add_edge(3, "N8", "N1", [("p1", Prop::U64(1u64))], Some("layer1")) - // .add_edge(4, "N8", "N1", [("p1", Prop::U64(2u64))], Some("layer2")) - // 1. As per layer graph semantics, every edge update belongs to a particular layer (or '_default' if no layer specified). - // This means the last_before is computed per layer and not across layers. In other words, when computing - // last_before for (N8->N1, layer1) and window(6, 9), t = 3 is the correct last before edge update timestamp and not t = 4 - // because t=4 edge update is in layer2. - // 2. Since the search is conducted across both the layers i.e., layer1 and layer2, the results are union of - // results from both layer1 and layer2. - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]); - - let layers = vec!["layer1".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!( - results, - vec!["N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N1"] - ); + #[test] + fn test_edges_filters_pg() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, layers, expected_results); + assert_search_results!(search_edges_pg, filter, layers, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec![ + "N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", "N8->N1", + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, layers, expected_results); + assert_search_results!(search_edges_pg, filter, layers, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2->N3", "N5->N6", "N8->N1"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, layers, expected_results); + assert_search_results!(search_edges_pg, filter, layers, expected_results); + } - let layers = vec!["layer2".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, filter, layers); - assert_eq!(results, vec!["N1->N2", "N6->N7", "N7->N8"]); + #[test] + fn test_edges_filters_pg_w123() { + let layers: Vec = vec!["layer1".into(), "layer2".into()]; + let filter = PropertyFilter::property("p1").eq(1u64); + + // Why is the edge N8 -> N1 included in the results? + // The reason edge N8 -> N1 is included as part of the results because of following two semantic reasons: + // .add_edge(3, "N8", "N1", [("p1", Prop::U64(1u64))], Some("layer1")) + // .add_edge(4, "N8", "N1", [("p1", Prop::U64(2u64))], Some("layer2")) + // 1. As per layer graph semantics, every edge update belongs to a particular layer (or '_default' if no layer specified). + // This means the last_before is computed per layer and not across layers. In other words, when computing + // last_before for (N8->N1, layer1) and window(6, 9), t = 3 is the correct last before edge update timestamp and not t = 4 + // because t=4 edge update is in layer2. + // 2. Since the search is conducted across both the layers i.e., layer1 and layer2, the results are union of + // results from both layer1 and layer2. + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer1".into()]; + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = + vec!["N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N1"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, layers, 6..9, expected_results); + + let layers: Vec = vec!["layer2".into()]; + let filter = PropertyFilter::property("p1").ge(2u64); + let expected_results = vec!["N2->N3", "N5->N6", "N8->N1"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, layers, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, layers, 6..9, expected_results); + } } } } diff --git a/raphtory/src/db/graph/views/mod.rs b/raphtory/src/db/graph/views/mod.rs index 4dba9bed1d..c933cb707c 100644 --- a/raphtory/src/db/graph/views/mod.rs +++ b/raphtory/src/db/graph/views/mod.rs @@ -1,7 +1,466 @@ pub mod cached_view; pub mod deletion_graph; +pub mod filter; pub mod layer_graph; pub mod node_subgraph; -pub mod node_type_filtered_subgraph; -pub mod property_filter; pub mod window_graph; + +#[macro_export] +pub mod macros { + #[macro_export] + macro_rules! assert_filter_results_w { + ($filter_fn:ident, $filter:expr, $window:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $window); + assert_eq!($expected_results, filter_results); + }}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $window:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $window); + assert_eq!($expected_results, search_results); + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $window:expr, $expected_results:expr) => {}; + } +} + +#[macro_export] +pub mod macros_nodes { + #[macro_export] + macro_rules! assert_filter_nodes_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {{ + let filter_results = filter_nodes_with($filter.clone(), $init_fn(Graph::new())); + assert_eq!($expected_results, filter_results); + + let filter_results = + filter_nodes_with($filter.clone(), $init_fn(PersistentGraph::new())); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let filter_results = filter_nodes_with($filter.clone(), dgs.clone().into_graph()); + assert_eq!($expected_results, filter_results); + + let filter_results = + filter_nodes_with($filter.clone(), dgs.into_persistent_graph()); + assert_eq!($expected_results, filter_results); + } + }}; + } + + #[macro_export] + macro_rules! assert_filter_nodes_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let filter_results = filter_nodes_with( + $filter.clone(), + $init_fn(Graph::new()).window($w.start, $w.end), + ); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let filter_results = filter_nodes_with( + $filter.clone(), + dgs.clone().into_graph().window($w.start, $w.end), + ); + assert_eq!($expected_results, filter_results); + } + }}; + } + + #[macro_export] + macro_rules! assert_filter_nodes_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let filter_results = filter_nodes_with( + $filter.clone(), + $init_fn(PersistentGraph::new()).window($w.start, $w.end), + ); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let filter_results = filter_nodes_with( + $filter.clone(), + dgs.into_persistent_graph().window($w.start, $w.end), + ); + assert_eq!($expected_results, filter_results); + } + }}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_nodes_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {{ + let filter_results = search_nodes_with($filter.clone(), $init_fn(Graph::new())); + assert_eq!($expected_results, filter_results); + + let search_results = + search_nodes_with($filter.clone(), $init_fn(PersistentGraph::new())); + assert_eq!($expected_results, search_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let search_results = search_nodes_with($filter.clone(), dgs.clone().into_graph()); + assert_eq!($expected_results, search_results); + + let search_results = + search_nodes_with($filter.clone(), dgs.into_persistent_graph()); + assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_nodes_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_nodes_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let filter_results = search_nodes_with( + $filter.clone(), + $init_fn(Graph::new()).window($w.start, $w.end), + ); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let search_results = search_nodes_with( + $filter.clone(), + dgs.clone().into_graph().window($w.start, $w.end), + ); + assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_nodes_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_nodes_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let search_results = search_nodes_with( + $filter.clone(), + $init_fn(PersistentGraph::new()).window($w.start, $w.end), + ); + assert_eq!($expected_results, search_results); + + #[cfg(feature = "storage")] + { + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let search_results = search_nodes_with( + $filter.clone(), + dgs.into_persistent_graph().window($w.start, $w.end), + ); + assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_nodes_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {}; + } +} + +#[macro_export] +pub mod macros_edges { + #[macro_export] + macro_rules! assert_filter_edges_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {{ + let filter_results = filter_edges_with($filter.clone(), $init_fn(Graph::new())); + assert_eq!($expected_results, filter_results); + + // TODO: PropertyFilteringNotImplemented + // let filter_results = + // filter_edges_with($filter.clone(), $init_fn(PersistentGraph::new())); + // assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let filter_results = filter_edges_with($filter.clone(), dgs.clone().into_graph()); + assert_eq!($expected_results, filter_results); + + // TODO: PropertyFilteringNotImplemented + // let filter_results = + // filter_edges_with($filter.clone(), dgs.into_persistent_graph()); + // assert_eq!($expected_results, filter_results); + } + }}; + } + + #[macro_export] + macro_rules! assert_filter_edges_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let filter_results = filter_edges_with($filter.clone(), $init_fn(Graph::new()).window($w.start, $w.end)); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + // let filter_results = + // filter_edges_with($filter.clone(), dgs.clone().into_graph().window($w.start, $w.end)); + // assert_eq!($expected_results, filter_results); + } + }}; + } + + #[macro_export] + macro_rules! assert_filter_edges_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + // TODO: PropertyFilteringNotImplemented + // let filter_results = + // filter_edges_with($filter.clone(), $init_fn(PersistentGraph::new()).window($w.start, $w.end)); + // assert_eq!($expected_results, filter_results); + + // TODO: PropertyFilteringNotImplemented + // #[cfg(feature = "storage")] + // { + // use crate::disk_graph::DiskGraphStorage; + // use tempfile::TempDir; + // + // let graph = $init_fn(Graph::new()); + // let path = TempDir::new().unwrap(); + // let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + // let filter_results = + // filter_edges_with($filter.clone(), dgs.into_persistent_graph().window($w.start, $w.end)); + // assert_eq!($expected_results, filter_results); + // } + }}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_edges_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {{ + let filter_results = search_edges_with($filter.clone(), $init_fn(Graph::new())); + assert_eq!($expected_results, filter_results); + + // let search_results = + // search_edges_with($filter.clone(), $init_fn(PersistentGraph::new())); + // assert_eq!($expected_results, search_results); + + #[cfg(feature = "storage")] + { + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + // let search_results = + // search_edges_with($filter.clone(), dgs.clone().into_graph()); + // assert_eq!($expected_results, search_results); + + // let search_results = + // search_edges_with($filter.clone(), dgs.into_persistent_graph()); + // assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_edges_results { + ($init_fn:ident, $filter:expr, $expected_results:expr) => {}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_edges_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let filter_results = search_edges_with($filter.clone(), $init_fn(Graph::new()).window($w.start, $w.end)); + assert_eq!($expected_results, filter_results); + + #[cfg(feature = "storage")] + { + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + // let search_results = + // search_edges_with($filter.clone(), dgs.clone().into_graph().window($w.start, $w.end)); + // assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_edges_results_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {}; + } + + #[macro_export] + #[cfg(feature = "search")] + macro_rules! assert_search_edges_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {{ + let search_results = search_edges_with( + $filter.clone(), + $init_fn(PersistentGraph::new()).window($w.start, $w.end), + ); + assert_eq!($expected_results, search_results); + + #[cfg(feature = "storage")] + { + use crate::disk_graph::DiskGraphStorage; + use tempfile::TempDir; + + let graph = $init_fn(Graph::new()); + let path = TempDir::new().unwrap(); + let dgs = DiskGraphStorage::from_graph(&graph, &path).unwrap(); + + let search_results = search_edges_with( + $filter.clone(), + dgs.into_persistent_graph().window($w.start, $w.end), + ); + assert_eq!($expected_results, search_results); + } + }}; + } + + #[macro_export] + #[cfg(not(feature = "search"))] + macro_rules! assert_search_edges_results_pg_w { + ($init_fn:ident, $filter:expr, $w:expr, $expected_results:expr) => {}; + } +} + +mod test_helpers { + #[cfg(feature = "search")] + pub use crate::db::api::view::SearchableGraphOps; + use crate::{ + db::{ + api::view::StaticGraphViewOps, + graph::views::filter::{ + internal::{InternalEdgeFilterOps, InternalNodeFilterOps}, + model::{AsEdgeFilter, AsNodeFilter}, + }, + }, + prelude::{ + EdgePropertyFilterOps, EdgeViewOps, GraphViewOps, NodePropertyFilterOps, NodeViewOps, + }, + }; + + pub(crate) fn filter_nodes_with(filter: I, graph: G) -> Vec + where + G: StaticGraphViewOps, + { + let mut results = graph + .filter_nodes(filter) + .unwrap() + .nodes() + .iter() + .map(|n| n.name()) + .collect::>(); + results.sort(); + results + } + + #[cfg(feature = "search")] + pub(crate) fn search_nodes_with(filter: I, graph: G) -> Vec + where + G: StaticGraphViewOps, + { + graph.create_index_in_ram().unwrap(); + + let mut results = graph + .search_nodes(filter, 20, 0) + .unwrap() + .into_iter() + .map(|nv| nv.name()) + .collect::>(); + results.sort(); + results + } + + pub(crate) fn filter_edges_with(filter: I, graph: G) -> Vec + where + G: StaticGraphViewOps, + { + let mut results = graph + .filter_edges(filter) + .unwrap() + .edges() + .iter() + .map(|e| format!("{}->{}", e.src().name(), e.dst().name())) + .collect::>(); + results.sort(); + results + } + + #[cfg(feature = "search")] + pub(crate) fn search_edges_with(filter: I, graph: G) -> Vec + where + G: StaticGraphViewOps, + { + graph.create_index_in_ram().unwrap(); + + let mut results = graph + .search_edges(filter, 20, 0) + .unwrap() + .into_iter() + .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) + .collect::>(); + results.sort(); + results + } +} diff --git a/raphtory/src/db/graph/views/node_subgraph.rs b/raphtory/src/db/graph/views/node_subgraph.rs index c6af73ddc2..5a4d1d5f54 100644 --- a/raphtory/src/db/graph/views/node_subgraph.rs +++ b/raphtory/src/db/graph/views/node_subgraph.rs @@ -110,7 +110,7 @@ impl<'graph, G: GraphViewOps<'graph>> NodeFilterOps for NodeSubgraph { impl<'graph, G: GraphViewOps<'graph>> ListOps for NodeSubgraph { fn node_list(&self) -> NodeList { NodeList::List { - nodes: self.nodes.clone(), + elems: self.nodes.clone(), } } @@ -228,321 +228,516 @@ mod subgraph_tests { ); } - #[cfg(all(test, feature = "search"))] - mod search_nodes_node_subgraph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, - }, - }, - prelude::{AdditionOps, Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - let nodes = vec![ - (6, "N1", vec![("p1", Prop::U64(2u64))]), - (7, "N1", vec![("p1", Prop::U64(1u64))]), - (6, "N2", vec![("p1", Prop::U64(1u64))]), - (7, "N2", vec![("p1", Prop::U64(2u64))]), - (8, "N3", vec![("p1", Prop::U64(1u64))]), - (9, "N4", vec![("p1", Prop::U64(1u64))]), - (5, "N5", vec![("p1", Prop::U64(1u64))]), - (6, "N5", vec![("p1", Prop::U64(2u64))]), - (5, "N6", vec![("p1", Prop::U64(1u64))]), - (6, "N6", vec![("p1", Prop::U64(1u64))]), - (3, "N7", vec![("p1", Prop::U64(1u64))]), - (5, "N7", vec![("p1", Prop::U64(1u64))]), - (3, "N8", vec![("p1", Prop::U64(1u64))]), - (4, "N8", vec![("p1", Prop::U64(2u64))]), - ]; - - for (id, name, props) in &nodes { - graph.add_node(*id, name, props.clone(), None).unwrap(); - } + #[test] + fn test_layer_edges() { + let graph = Graph::new(); + graph.add_edge(0, 0, 1, NO_PROPS, Some("1")).unwrap(); + graph.add_edge(1, 0, 1, NO_PROPS, Some("2")).unwrap(); + assert_eq!( + graph.subgraph([0, 1]).edges().id().collect_vec(), + [(GID::U64(0), GID::U64(1))] + ); + assert_eq!( graph - } + .subgraph([0, 1]) + .valid_layers("1") + .edges() + .id() + .collect_vec(), + [(GID::U64(0), GID::U64(1))] + ); + } - fn search_nodes_by_composite_filter( - graph: &G, - node_names: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .subgraph(node_names) - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } + mod test_filters_node_subgraph { - fn search_nodes_by_composite_filter_w( - graph: &G, - w: Range, - node_names: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .subgraph(node_names) - .window(w.start, w.end) - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results + macro_rules! assert_filter_results { + ($filter_fn:ident, $filter:expr, $node_names:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $node_names.clone()); + assert_eq!($expected_results, filter_results); + }}; } - #[test] - fn test_search_nodes_subgraph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N3", "N4"]); + macro_rules! assert_filter_results_w { + ($filter_fn:ident, $filter:expr, $node_names:expr, $window:expr, $expected_results:expr) => {{ + let filter_results = $filter_fn($filter.clone(), $window, $node_names.clone()); + assert_eq!($expected_results, filter_results); + }}; } - #[test] - fn test_search_nodes_subgraph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); - let filter = PropertyFilter::property("p1").eq(1u64); + #[cfg(feature = "search")] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $node_names:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $node_names); + assert_eq!($expected_results, search_results); + }}; + } - let node_names = graph.nodes().name().collect_vec(); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N1", "N3", "N6"]); + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results { + ($search_fn:ident, $filter:expr, $node_names:expr, $expected_results:expr) => {}; + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; + #[cfg(feature = "search")] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $node_names:expr, $window:expr, $expected_results:expr) => {{ + let search_results = $search_fn($filter.clone(), $window, $node_names); + assert_eq!($expected_results, search_results); + }}; + } - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N3"]); + #[cfg(not(feature = "search"))] + macro_rules! assert_search_results_w { + ($search_fn:ident, $filter:expr, $node_names:expr, $window:expr, $expected_results:expr) => {}; } - #[test] - fn test_search_nodes_persistent_subgraph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + mod test_nodes_filters_node_subgraph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, filter::model::PropertyFilterOps, + }, + }, + prelude::{AdditionOps, Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, + }; + use std::ops::Range; + + use crate::db::graph::views::{ + filter::internal::InternalNodeFilterOps, test_helpers::filter_nodes_with, + }; + + fn init_graph(graph: G) -> G { + let nodes = vec![ + (6, "N1", vec![("p1", Prop::U64(2u64))]), + (7, "N1", vec![("p1", Prop::U64(1u64))]), + (6, "N2", vec![("p1", Prop::U64(1u64))]), + (7, "N2", vec![("p1", Prop::U64(2u64))]), + (8, "N3", vec![("p1", Prop::U64(1u64))]), + (9, "N4", vec![("p1", Prop::U64(1u64))]), + (5, "N5", vec![("p1", Prop::U64(1u64))]), + (6, "N5", vec![("p1", Prop::U64(2u64))]), + (5, "N6", vec![("p1", Prop::U64(1u64))]), + (6, "N6", vec![("p1", Prop::U64(1u64))]), + (3, "N7", vec![("p1", Prop::U64(1u64))]), + (5, "N7", vec![("p1", Prop::U64(1u64))]), + (3, "N8", vec![("p1", Prop::U64(1u64))]), + (4, "N8", vec![("p1", Prop::U64(2u64))]), + ]; + + for (id, name, props) in &nodes { + graph.add_node(*id, name, props.clone(), None).unwrap(); + } + + graph + } - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); + fn filter_nodes( + filter: I, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_nodes_with(filter, graph.subgraph(node_names)) + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N3", "N4"]); - } + fn filter_nodes_w( + filter: I, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_nodes_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } - #[test] - fn test_search_nodes_persistent_subgraph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + fn filter_nodes_pg( + filter: I, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_nodes_with(filter, graph.subgraph(node_names)) + } - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); + fn filter_nodes_pg_w( + filter: I, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_nodes_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N3"]); - } - } + #[cfg(feature = "search")] + mod search_nodes { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::node_subgraph::subgraph_tests::test_filters_node_subgraph::test_nodes_filters_node_subgraph::init_graph; + use crate::db::graph::views::test_helpers::search_nodes_with; + use crate::prelude::{Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}; + + pub fn search_nodes( + filter: PropertyFilter, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_nodes_with(filter, graph.subgraph(node_names)) + } + + pub fn search_nodes_w( + filter: PropertyFilter, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_nodes_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } + + pub fn search_nodes_pg( + filter: PropertyFilter, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_nodes_with(filter, graph.subgraph(node_names)) + } + + pub fn search_nodes_pg_w( + filter: PropertyFilter, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_nodes_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } + } - #[cfg(all(test, feature = "search"))] - mod search_edges_node_subgraph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, - }, - }, - prelude::{ - AdditionOps, EdgeViewOps, Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps, - }, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - let edges = vec![ - (6, "N1", "N2", vec![("p1", Prop::U64(2u64))]), - (7, "N1", "N2", vec![("p1", Prop::U64(1u64))]), - (6, "N2", "N3", vec![("p1", Prop::U64(1u64))]), - (7, "N2", "N3", vec![("p1", Prop::U64(2u64))]), - (8, "N3", "N4", vec![("p1", Prop::U64(1u64))]), - (9, "N4", "N5", vec![("p1", Prop::U64(1u64))]), - (5, "N5", "N6", vec![("p1", Prop::U64(1u64))]), - (6, "N5", "N6", vec![("p1", Prop::U64(2u64))]), - (5, "N6", "N7", vec![("p1", Prop::U64(1u64))]), - (6, "N6", "N7", vec![("p1", Prop::U64(1u64))]), - (3, "N7", "N8", vec![("p1", Prop::U64(1u64))]), - (5, "N7", "N8", vec![("p1", Prop::U64(1u64))]), - (3, "N8", "N1", vec![("p1", Prop::U64(1u64))]), - (4, "N8", "N1", vec![("p1", Prop::U64(2u64))]), - ]; - - for (id, src, tgt, props) in &edges { - graph.add_edge(*id, src, tgt, props.clone(), None).unwrap(); + #[cfg(feature = "search")] + use search_nodes::*; + + #[test] + fn test_search_nodes_subgraph() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes, filter, None, expected_results); + assert_search_results!(search_nodes, filter, None, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N3", "N4"]; + assert_filter_results!(filter_nodes, filter, node_names, expected_results); + assert_search_results!(search_nodes, filter, node_names, expected_results); } - graph - } + #[test] + fn test_search_nodes_subgraph_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_results_w!(filter_nodes_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_nodes_w, filter, None, 6..9, expected_results); + + let node_names: Option> = Some(vec!["N3".into()]); + let filter = PropertyFilter::property("p1").gt(0u64); + let expected_results = vec!["N3"]; + assert_filter_results_w!( + filter_nodes_w, + filter, + node_names, + 6..9, + expected_results + ); + assert_search_results_w!( + search_nodes_w, + filter, + node_names, + 6..9, + expected_results + ); + } - fn search_edges_by_composite_filter( - graph: &G, - node_names: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .subgraph(node_names) - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } + #[test] + fn test_search_nodes_persistent_subgraph() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N4", "N6", "N7"]; + assert_filter_results!(filter_nodes_pg, filter, None, expected_results); + assert_search_results!(search_nodes_pg, filter, None, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N3", "N4"]; + assert_filter_results!(filter_nodes_pg, filter, node_names, expected_results); + assert_search_results!(search_nodes_pg, filter, node_names, expected_results); + } - fn search_edges_by_composite_filter_w( - graph: &G, - w: Range, - node_names: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .subgraph(node_names) - .window(w.start, w.end) - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results + #[test] + fn test_search_nodes_persistent_subgraph_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6", "N7"]; + assert_filter_results_w!(filter_nodes_pg_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_nodes_pg_w, filter, None, 6..9, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec!["N2", "N3", "N5"]; + assert_filter_results_w!( + filter_nodes_pg_w, + filter, + node_names, + 6..9, + expected_results + ); + assert_search_results_w!( + search_nodes_pg_w, + filter, + node_names, + 6..9, + expected_results + ); + } } - #[test] - fn test_search_edges_subgraph() { - let graph = Graph::new(); - let graph = init_graph(graph); + mod test_edges_filters_node_subgraph { + use crate::{ + core::Prop, + db::{ + api::view::StaticGraphViewOps, + graph::views::{ + deletion_graph::PersistentGraph, + filter::{internal::InternalEdgeFilterOps, model::PropertyFilterOps}, + }, + }, + prelude::{AdditionOps, Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}, + }; + use std::ops::Range; + + use crate::db::graph::views::test_helpers::filter_edges_with; + + fn init_graph(graph: G) -> G { + let edges = vec![ + (6, "N1", "N2", vec![("p1", Prop::U64(2u64))]), + (7, "N1", "N2", vec![("p1", Prop::U64(1u64))]), + (6, "N2", "N3", vec![("p1", Prop::U64(1u64))]), + (7, "N2", "N3", vec![("p1", Prop::U64(2u64))]), + (8, "N3", "N4", vec![("p1", Prop::U64(1u64))]), + (9, "N4", "N5", vec![("p1", Prop::U64(1u64))]), + (5, "N5", "N6", vec![("p1", Prop::U64(1u64))]), + (6, "N5", "N6", vec![("p1", Prop::U64(2u64))]), + (5, "N6", "N7", vec![("p1", Prop::U64(1u64))]), + (6, "N6", "N7", vec![("p1", Prop::U64(1u64))]), + (3, "N7", "N8", vec![("p1", Prop::U64(1u64))]), + (5, "N7", "N8", vec![("p1", Prop::U64(1u64))]), + (3, "N8", "N1", vec![("p1", Prop::U64(1u64))]), + (4, "N8", "N1", vec![("p1", Prop::U64(2u64))]), + ]; + + for (id, src, tgt, props) in &edges { + graph.add_edge(*id, src, tgt, props.clone(), None).unwrap(); + } + + graph + } - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_names, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); + fn filter_edges( + filter: I, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_edges_with(filter, graph.subgraph(node_names)) + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N3->N4", "N4->N5"]); - } + fn filter_edges_w( + filter: I, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_edges_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } - #[test] - fn test_search_edges_subgraph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); + #[allow(dead_code)] + fn filter_edges_pg( + filter: I, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_edges_with(filter, graph.subgraph(node_names)) + } - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7"]); + #[allow(dead_code)] + fn filter_edges_pg_w( + filter: I, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + filter_edges_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N3->N4"]); - } + #[cfg(feature = "search")] + mod search_edges { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::node_subgraph::subgraph_tests::test_filters_node_subgraph::test_edges_filters_node_subgraph::init_graph; + use crate::db::graph::views::test_helpers::search_edges_with; + use crate::prelude::{Graph, GraphViewOps, NodeViewOps, PropertyFilter, TimeOps}; + + pub fn search_edges( + filter: PropertyFilter, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_edges_with(filter, graph.subgraph(node_names)) + } + + pub fn search_edges_w( + filter: PropertyFilter, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(Graph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_edges_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } + + pub fn search_edges_pg( + filter: PropertyFilter, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_edges_with(filter, graph.subgraph(node_names)) + } + + pub fn search_edges_pg_w( + filter: PropertyFilter, + w: Range, + node_names: Option>, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + let node_names: Vec = + node_names.unwrap_or_else(|| graph.nodes().name().collect()); + search_edges_with(filter, graph.subgraph(node_names).window(w.start, w.end)) + } + } - #[test] - fn test_search_edges_persistent_subgraph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); + #[cfg(feature = "search")] + use search_edges::*; + + #[test] + fn test_edges_filters() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + assert_filter_results!(filter_edges, filter, None, expected_results); + assert_search_results!(search_edges, filter, None, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N3->N4", "N4->N5"]; + assert_filter_results!(filter_edges, filter, node_names, expected_results); + assert_search_results!(search_edges, filter, node_names, expected_results); + } - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_names, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); + #[test] + fn test_edges_filters_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, None, 6..9, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec!["N2->N3", "N3->N4"]; + assert_filter_results_w!( + filter_edges_w, + filter, + node_names, + 6..9, + expected_results + ); + assert_search_results_w!( + search_edges_w, + filter, + node_names, + 6..9, + expected_results + ); + } - let node_names: Vec = vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_names, filter); - assert_eq!(results, vec!["N3->N4", "N4->N5"]); - } + #[test] + fn test_edges_filters_pg() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, None, expected_results); + assert_search_results!(search_edges_pg, filter, None, expected_results); + + let node_names: Option> = + Some(vec!["N2".into(), "N3".into(), "N4".into(), "N5".into()]); + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N3->N4", "N4->N5"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results!(filter_edges_pg, filter, node_names, expected_results); + assert_search_results!(search_edges_pg, filter, node_names, expected_results); + } - #[test] - fn test_search_edges_persistent_subgraph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let node_names = graph.nodes().name().collect_vec(); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]); - - let node_names: Vec = vec![ - "N2".into(), - "N3".into(), - "N4".into(), - "N5".into(), - "N6".into(), - ]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_names, filter); - assert_eq!(results, vec!["N3->N4"]); + #[test] + fn test_edges_filters_pg_w() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, None, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, None, 6..9, expected_results); + + let node_names: Option> = Some(vec![ + "N2".into(), + "N3".into(), + "N4".into(), + "N5".into(), + "N6".into(), + ]); + let filter = PropertyFilter::property("p1").lt(2u64); + let expected_results = vec!["N3->N4"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, node_names, 6..9, expected_results); + assert_search_results_w!( + search_edges_pg_w, + filter, + node_names, + 6..9, + expected_results + ); + } } } - - #[test] - fn test_layer_edges() { - let graph = Graph::new(); - graph.add_edge(0, 0, 1, NO_PROPS, Some("1")).unwrap(); - graph.add_edge(1, 0, 1, NO_PROPS, Some("2")).unwrap(); - - assert_eq!( - graph.subgraph([0, 1]).edges().id().collect_vec(), - [(GID::U64(0), GID::U64(1))] - ); - assert_eq!( - graph - .subgraph([0, 1]) - .valid_layers("1") - .edges() - .id() - .collect_vec(), - [(GID::U64(0), GID::U64(1))] - ); - } } diff --git a/raphtory/src/db/graph/views/node_type_filtered_subgraph.rs b/raphtory/src/db/graph/views/node_type_filtered_subgraph.rs deleted file mode 100644 index 26b844379f..0000000000 --- a/raphtory/src/db/graph/views/node_type_filtered_subgraph.rs +++ /dev/null @@ -1,464 +0,0 @@ -use crate::{ - core::entities::LayerIds, - db::api::{ - properties::internal::InheritPropertiesOps, - storage::graph::nodes::{node_ref::NodeStorageRef, node_storage_ops::NodeStorageOps}, - view::internal::{ - Base, Immutable, InheritCoreOps, InheritEdgeFilterOps, InheritEdgeHistoryFilter, - InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeHistoryFilter, - InheritTimeSemantics, NodeFilterOps, Static, - }, - }, - prelude::GraphViewOps, -}; -use std::sync::Arc; - -use crate::db::api::view::internal::InheritStorageOps; - -#[derive(Clone, Debug)] -pub struct TypeFilteredSubgraph { - pub(crate) graph: G, - pub(crate) node_types: Arc<[usize]>, -} - -impl Static for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> Base for TypeFilteredSubgraph { - type Base = G; - #[inline(always)] - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl<'graph, G: GraphViewOps<'graph>> TypeFilteredSubgraph { - pub fn new(graph: G, node_types: Vec) -> Self { - let node_types = node_types.into(); - Self { graph, node_types } - } -} - -impl<'graph, G: GraphViewOps<'graph>> Immutable for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritTimeSemantics for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeFilterOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter for TypeFilteredSubgraph {} - -impl<'graph, G: GraphViewOps<'graph>> NodeFilterOps for TypeFilteredSubgraph { - #[inline] - fn nodes_filtered(&self) -> bool { - true - } - - #[inline] - fn node_list_trusted(&self) -> bool { - false - } - - #[inline] - fn edge_filter_includes_node_filter(&self) -> bool { - false - } - - #[inline] - fn filter_node(&self, node: NodeStorageRef, layer_ids: &LayerIds) -> bool { - self.node_types.contains(&node.node_type_id()) && self.graph.filter_node(node, layer_ids) - } -} - -#[cfg(all(test, feature = "search"))] -mod search_nodes_node_type_filtered_subgraph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, - }, - }, - prelude::{AdditionOps, Graph, NodeViewOps, PropertyFilter, TimeOps}, - }; - use std::ops::Range; - - 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 - } - - fn search_nodes_by_composite_filter( - graph: &G, - node_types: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let sgm = graph.subgraph_node_types(node_types); - let mut results = sgm - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } - - fn search_nodes_by_composite_filter_w( - graph: &G, - w: Range, - node_types: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let sgm = graph.subgraph_node_types(node_types); - let mut results = sgm - .window(w.start, w.end) - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } - - 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() - } - - #[test] - fn test_search_nodes_type_filtered_subgraph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N7"]); - } - - #[test] - fn test_search_nodes_type_filtered_subgraph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N6"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1", "N3"]); - } - - #[test] - fn test_search_nodes_persistent_type_filtered_subgraph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N4", "N7"]); - } - - #[test] - fn test_search_nodes_persistent_type_filtered_subgraph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N6", "N7"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1", "N3", "N7"]); - } -} - -#[cfg(all(test, feature = "search"))] -mod search_edges_node_type_filtered_subgraph_tests { - use crate::{ - core::Prop, - db::{ - api::view::{SearchableGraphOps, StaticGraphViewOps}, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, PropertyFilterOps}, - }, - }, - prelude::{ - AdditionOps, EdgeViewOps, Graph, NodeViewOps, PropertyFilter, TimeOps, NO_PROPS, - }, - }; - use std::ops::Range; - - fn init_graph(graph: G) -> G { - let edges = vec![ - (6, "N1", "N2", vec![("p1", Prop::U64(2u64))], None), - (7, "N1", "N2", vec![("p1", Prop::U64(1u64))], None), - (6, "N2", "N3", vec![("p1", Prop::U64(1u64))], None), - (7, "N2", "N3", vec![("p1", Prop::U64(2u64))], None), - (8, "N3", "N4", vec![("p1", Prop::U64(1u64))], None), - (9, "N4", "N5", vec![("p1", Prop::U64(1u64))], None), - (5, "N5", "N6", vec![("p1", Prop::U64(1u64))], None), - (6, "N5", "N6", vec![("p1", Prop::U64(2u64))], None), - (5, "N6", "N7", vec![("p1", Prop::U64(1u64))], None), - (6, "N6", "N7", vec![("p1", Prop::U64(1u64))], None), - (3, "N7", "N8", vec![("p1", Prop::U64(1u64))], None), - (5, "N7", "N8", vec![("p1", Prop::U64(1u64))], None), - (3, "N8", "N1", vec![("p1", Prop::U64(1u64))], None), - (4, "N8", "N1", vec![("p1", Prop::U64(2u64))], None), - ]; - - 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 - } - - fn search_edges_by_composite_filter( - graph: &G, - node_types: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let sgm = graph.subgraph_node_types(node_types); - let mut results = sgm - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } - - fn search_edges_by_composite_filter_w( - graph: &G, - w: Range, - node_types: Vec, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let sgm = graph.subgraph_node_types(node_types); - let mut results = sgm - .window(w.start, w.end) - .search_edges(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } - - 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() - } - - #[test] - fn test_search_edges_type_filtered_subgraph() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_types, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N4->N5"]); - } - - #[test] - fn test_search_edges_type_filtered_subgraph_w() { - let graph = Graph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4"]); - } - - #[test] - fn test_search_edges_persistent_type_filtered_subgraph() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_types, filter); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter(&graph, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N4->N5"]); - } - - #[test] - fn test_search_edges_persistent_type_filtered_subgraph_w() { - let graph = PersistentGraph::new(); - let graph = init_graph(graph); - - let node_types = get_all_node_types(&graph); - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4", "N6->N7", "N7->N8"]); - - let node_types = vec!["air_nomad".into(), "water_tribe".into()]; - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges_by_composite_filter_w(&graph, 6..9, node_types, filter); - assert_eq!(results, vec!["N1->N2", "N3->N4"]); - } -} - -#[cfg(test)] -mod tests { - use crate::{db::graph::views::property_filter::PropertyRef, prelude::*}; - - #[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 - .filter_nodes(PropertyFilter::eq(PropertyRef::Property("p1".into()), 1u64)) - .unwrap() - .nodes(), - vec!["C", "D"] - ); - - assert!(type_filtered_subgraph - .filter_edges(PropertyFilter::eq(PropertyRef::Property("p1".into()), 1u64)) - .unwrap() - .edges() - .is_empty()) - } -} diff --git a/raphtory/src/db/graph/views/property_filter/exploded_edge_property_filter.rs b/raphtory/src/db/graph/views/property_filter/exploded_edge_property_filter.rs deleted file mode 100644 index 17b4737b7d..0000000000 --- a/raphtory/src/db/graph/views/property_filter/exploded_edge_property_filter.rs +++ /dev/null @@ -1,487 +0,0 @@ -use crate::{ - core::{entities::LayerIds, utils::errors::GraphError, Prop}, - db::{ - api::{ - properties::internal::InheritPropertiesOps, - storage::graph::{ - edges::{edge_ref::EdgeStorageRef, edge_storage_ops::EdgeStorageOps}, - nodes::node_ref::NodeStorageRef, - }, - view::{ - internal::{ - EdgeFilterOps, Immutable, InheritCoreOps, InheritEdgeHistoryFilter, - InheritLayerOps, InheritListOps, InheritMaterialize, InheritNodeFilterOps, - InheritNodeHistoryFilter, InternalLayerOps, Static, TimeSemantics, - }, - Base, BoxedLIter, IntoDynBoxed, - }, - }, - graph::views::property_filter::internal::InternalExplodedEdgeFilterOps, - }, - prelude::{GraphViewOps, PropertyFilter}, -}; -use raphtory_api::{ - core::{ - entities::{edges::edge_ref::EdgeRef, VID}, - storage::timeindex::TimeIndexEntry, - }, - iter::BoxedLDIter, -}; -use std::ops::Range; - -use crate::db::api::view::internal::InheritStorageOps; - -#[derive(Debug, Clone)] -pub struct ExplodedEdgePropertyFilteredGraph { - graph: G, - prop_id: Option, - filter: PropertyFilter, -} - -impl Static for ExplodedEdgePropertyFilteredGraph {} -impl Immutable for ExplodedEdgePropertyFilteredGraph {} - -impl<'graph, G: GraphViewOps<'graph>> ExplodedEdgePropertyFilteredGraph { - pub(crate) fn new(graph: G, prop_id: Option, filter: PropertyFilter) -> Self { - Self { - graph, - prop_id, - filter, - } - } - - fn filter(&self, e: EdgeRef, t: TimeIndexEntry, layer_ids: &LayerIds) -> bool { - self.filter.matches( - self.prop_id - .and_then(|prop_id| self.graph.temporal_edge_prop_at(e, prop_id, t, layer_ids)) - .as_ref(), - ) - } -} - -impl InternalExplodedEdgeFilterOps for PropertyFilter { - type ExplodedEdgeFiltered<'graph, G: GraphViewOps<'graph>> = - ExplodedEdgePropertyFilteredGraph; - - fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError> { - let t_prop_id = self.resolve_temporal_prop_ids(graph.edge_meta())?; - Ok(ExplodedEdgePropertyFilteredGraph::new( - graph.clone(), - t_prop_id, - self, - )) - } -} - -impl<'graph, G> Base for ExplodedEdgePropertyFilteredGraph { - type Base = G; - - fn base(&self) -> &Self::Base { - &self.graph - } -} - -impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for ExplodedEdgePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeHistoryFilter - for ExplodedEdgePropertyFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>> InheritEdgeHistoryFilter - for ExplodedEdgePropertyFilteredGraph -{ -} - -impl<'graph, G: GraphViewOps<'graph>> InheritStorageOps for ExplodedEdgePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritLayerOps for ExplodedEdgePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritListOps for ExplodedEdgePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritMaterialize for ExplodedEdgePropertyFilteredGraph {} -impl<'graph, G: GraphViewOps<'graph>> InheritNodeFilterOps - for ExplodedEdgePropertyFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>> InheritPropertiesOps - for ExplodedEdgePropertyFilteredGraph -{ -} -impl<'graph, G: GraphViewOps<'graph>> EdgeFilterOps for ExplodedEdgePropertyFilteredGraph { - fn edges_filtered(&self) -> bool { - true - } - - fn edge_list_trusted(&self) -> bool { - false - } - - fn filter_edge(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> bool { - self.graph.filter_edge(edge, layer_ids) - && self - .edge_exploded(edge.out_ref(), layer_ids) - .next() - .is_some() - } -} - -impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for ExplodedEdgePropertyFilteredGraph { - fn node_earliest_time(&self, v: VID) -> Option { - self.graph.node_earliest_time(v) - } - - fn node_latest_time(&self, v: VID) -> Option { - self.graph.node_latest_time(v) - } - - fn view_start(&self) -> Option { - self.graph.view_start() - } - - fn view_end(&self) -> Option { - self.graph.view_end() - } - - fn earliest_time_global(&self) -> Option { - self.graph.earliest_time_global() - } - - fn latest_time_global(&self) -> Option { - self.graph.latest_time_global() - } - - fn earliest_time_window(&self, start: i64, end: i64) -> Option { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.earliest_time_window(start, end) - } - - fn latest_time_window(&self, start: i64, end: i64) -> Option { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.latest_time_window(start, end) - } - - fn node_earliest_time_window(&self, v: VID, start: i64, end: i64) -> Option { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.node_earliest_time_window(v, start, end) - } - - fn node_latest_time_window(&self, v: VID, start: i64, end: i64) -> Option { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.node_latest_time_window(v, start, end) - } - - fn include_node_window(&self, v: NodeStorageRef, w: Range, layer_ids: &LayerIds) -> bool { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.include_node_window(v, w, layer_ids) - } - - fn include_edge_window( - &self, - edge: EdgeStorageRef, - w: Range, - layer_ids: &LayerIds, - ) -> bool { - self.edge_window_exploded(edge.out_ref(), w, layer_ids) - .next() - .is_some() - } - - fn node_history(&self, v: VID) -> BoxedLIter<'_, TimeIndexEntry> { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.node_history(v) - } - - fn node_history_window(&self, v: VID, w: Range) -> BoxedLIter<'_, TimeIndexEntry> { - // FIXME: this is potentially wrong but there is no way to fix this right now as nodes don't - // separate timestamps from node property updates and edge additions currently - self.graph.node_history_window(v, w) - } - - fn node_edge_history<'a>( - &'a self, - v: VID, - w: Option>, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph.node_edge_history(v, w) - } - - fn node_history_rows( - &self, - v: VID, - w: Option>, - ) -> BoxedLIter<(TimeIndexEntry, Vec<(usize, Prop)>)> { - self.graph.node_history_rows(v, w) - } - - fn node_property_history<'a>( - &'a self, - v: VID, - w: Option>, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph.node_property_history(v, w) - } - - fn edge_history<'a>( - &'a self, - e: EdgeRef, - layer_ids: &'a LayerIds, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph - .edge_history(e, layer_ids) - .filter(move |t| self.filter(e, *t, layer_ids)) - .into_dyn_boxed() - } - - fn edge_history_window<'a>( - &'a self, - e: EdgeRef, - layer_ids: &'a LayerIds, - w: Range, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph - .edge_history_window(e, layer_ids, w) - .filter(move |t| self.filter(e, *t, layer_ids)) - .into_dyn_boxed() - } - - fn edge_exploded_count(&self, edge: EdgeStorageRef, layer_ids: &LayerIds) -> usize { - self.edge_exploded(edge.out_ref(), layer_ids).count() - } - - fn edge_exploded_count_window( - &self, - edge: EdgeStorageRef, - layer_ids: &LayerIds, - w: Range, - ) -> usize { - self.edge_window_exploded(edge.out_ref(), w, layer_ids) - .count() - } - - fn edge_exploded<'a>(&'a self, e: EdgeRef, layer_ids: &'a LayerIds) -> BoxedLIter<'a, EdgeRef> { - self.graph - .edge_exploded(e, layer_ids) - .filter(move |&e| { - self.filter( - e, - e.time().expect("exploded edge should have timestamp"), - layer_ids, - ) - }) - .into_dyn_boxed() - } - - fn edge_layers<'a>(&'a self, e: EdgeRef, layer_ids: &'a LayerIds) -> BoxedLIter<'a, EdgeRef> { - self.graph - .edge_layers(e, layer_ids) - .filter(move |&e| self.edge_exploded(e, layer_ids).next().is_some()) - .into_dyn_boxed() - } - - fn edge_window_exploded<'a>( - &'a self, - e: EdgeRef, - w: Range, - layer_ids: &'a LayerIds, - ) -> BoxedLIter<'a, EdgeRef> { - self.graph - .edge_window_exploded(e, w, layer_ids) - .filter(move |&e| { - self.filter( - e, - e.time().expect("exploded edge should have timestamp"), - layer_ids, - ) - }) - .into_dyn_boxed() - } - - fn edge_window_layers<'a>( - &'a self, - e: EdgeRef, - w: Range, - layer_ids: &'a LayerIds, - ) -> BoxedLIter<'a, EdgeRef> { - self.graph - .edge_window_layers(e, w.clone(), layer_ids) - .filter(move |&e| { - self.edge_window_exploded( - e, - w.clone(), - &LayerIds::One(e.layer().expect("exploded edge should have layer")), - ) - .next() - .is_some() - }) - .into_dyn_boxed() - } - - fn edge_earliest_time(&self, e: EdgeRef, layer_ids: &LayerIds) -> Option { - self.edge_exploded(e, layer_ids) - .next() - .map(|e| e.time_t().unwrap()) - } - - fn edge_earliest_time_window( - &self, - e: EdgeRef, - w: Range, - layer_ids: &LayerIds, - ) -> Option { - self.edge_window_exploded(e, w, layer_ids) - .next() - .map(|e| e.time_t().unwrap()) - } - - fn edge_latest_time(&self, e: EdgeRef, layer_ids: &LayerIds) -> Option { - // FIXME: this is inefficient, need exploded to return something more useful - self.edge_exploded(e, layer_ids) - .last() - .map(|e| e.time_t().unwrap()) - } - - fn edge_latest_time_window( - &self, - e: EdgeRef, - w: Range, - layer_ids: &LayerIds, - ) -> Option { - // FIXME: this is inefficient, need exploded to return something more useful - self.edge_window_exploded(e, w, layer_ids) - .last() - .map(|e| e.time_t().unwrap()) - } - - fn edge_deletion_history<'a>( - &'a self, - e: EdgeRef, - layer_ids: &'a LayerIds, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph - .edge_deletion_history(e, layer_ids) - .filter(move |t| self.filter(e, t.previous(), layer_ids)) - .into_dyn_boxed() - } - - fn edge_deletion_history_window<'a>( - &'a self, - e: EdgeRef, - w: Range, - layer_ids: &'a LayerIds, - ) -> BoxedLIter<'a, TimeIndexEntry> { - self.graph - .edge_deletion_history_window(e, w, layer_ids) - .filter(move |t| self.filter(e, t.previous(), layer_ids)) - .into_dyn_boxed() - } - - fn edge_is_valid(&self, e: EdgeRef, layer_ids: &LayerIds) -> bool { - // FIXME: this is probably not correct - self.graph.edge_is_valid(e, layer_ids) - } - - fn edge_is_valid_at_end(&self, e: EdgeRef, layer_ids: &LayerIds, t: i64) -> bool { - // FIXME: this is probably not correct - self.graph.edge_is_valid_at_end(e, layer_ids, t) - } - - fn has_temporal_prop(&self, prop_id: usize) -> bool { - self.graph.has_temporal_prop(prop_id) - } - - fn temporal_prop_vec(&self, prop_id: usize) -> Vec<(i64, Prop)> { - self.graph.temporal_prop_vec(prop_id) - } - - fn temporal_prop_iter(&self, prop_id: usize) -> BoxedLIter<(i64, Prop)> { - self.graph.temporal_prop_iter(prop_id) - } - - fn temporal_prop_iter_window( - &self, - prop_id: usize, - start: i64, - end: i64, - ) -> BoxedLIter<(i64, Prop)> { - self.graph.temporal_prop_iter_window(prop_id, start, end) - } - - fn has_temporal_prop_window(&self, prop_id: usize, w: Range) -> bool { - self.graph.has_temporal_prop_window(prop_id, w) - } - - fn temporal_prop_vec_window(&self, prop_id: usize, start: i64, end: i64) -> Vec<(i64, Prop)> { - self.graph.temporal_prop_vec_window(prop_id, start, end) - } - fn temporal_node_prop_hist(&self, v: VID, id: usize) -> BoxedLDIter<(TimeIndexEntry, Prop)> { - // FIXME: this is wrong as we should not include filtered-out edges here - self.graph.temporal_node_prop_hist(v, id) - } - fn temporal_node_prop_hist_window( - &self, - v: VID, - id: usize, - start: i64, - end: i64, - ) -> BoxedLDIter<(TimeIndexEntry, Prop)> { - // FIXME: this is wrong as we should not include filtered-out edges here - self.graph.temporal_node_prop_hist_window(v, id, start, end) - } - - fn temporal_edge_prop_hist_window<'a>( - &'a self, - e: EdgeRef, - id: usize, - start: i64, - end: i64, - layer_ids: &LayerIds, - ) -> BoxedLIter<'a, (TimeIndexEntry, Prop)> { - self.graph - .temporal_edge_prop_hist_window(e, id, start, end, layer_ids) - .filter(move |(ti, _)| self.filter(e, *ti, self.layer_ids())) - .into_dyn_boxed() - } - - fn temporal_edge_prop_at( - &self, - e: EdgeRef, - id: usize, - t: TimeIndexEntry, - layer_ids: &LayerIds, - ) -> Option { - self.graph - .temporal_edge_prop_at(e, id, t, layer_ids) - .filter(move |_| self.filter(e, t, layer_ids)) - } - - fn temporal_edge_prop_hist<'a>( - &'a self, - e: EdgeRef, - id: usize, - layer_ids: &LayerIds, - ) -> BoxedLIter<'a, (TimeIndexEntry, Prop)> { - self.graph - .temporal_edge_prop_hist(e, id, layer_ids) - .filter(move |(ti, _)| self.filter(e, *ti, self.layer_ids())) - .into_dyn_boxed() - } - - fn constant_edge_prop(&self, e: EdgeRef, id: usize, layer_ids: &LayerIds) -> Option { - self.graph.constant_edge_prop(e, id, layer_ids) - } - - fn constant_edge_prop_window( - &self, - e: EdgeRef, - id: usize, - layer_ids: &LayerIds, - w: Range, - ) -> Option { - self.graph.constant_edge_prop_window(e, id, layer_ids, w) - } -} diff --git a/raphtory/src/db/graph/views/property_filter/internal.rs b/raphtory/src/db/graph/views/property_filter/internal.rs deleted file mode 100644 index 04cf998731..0000000000 --- a/raphtory/src/db/graph/views/property_filter/internal.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{core::utils::errors::GraphError, prelude::GraphViewOps}; - -pub trait InternalEdgeFilterOps: Sized { - type EdgeFiltered<'graph, G>: GraphViewOps<'graph> - where - G: GraphViewOps<'graph>, - Self: 'graph; - - fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError>; -} - -pub trait InternalExplodedEdgeFilterOps: Sized { - type ExplodedEdgeFiltered<'graph, G: GraphViewOps<'graph>>: GraphViewOps<'graph> - where - Self: 'graph; - - fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError>; -} - -pub trait InternalNodePropertyFilterOps: Sized { - type NodePropertyFiltered<'graph, G>: GraphViewOps<'graph> - where - Self: 'graph, - G: GraphViewOps<'graph>; - - fn create_node_property_filter<'graph, G: GraphViewOps<'graph>>( - self, - graph: G, - ) -> Result, GraphError>; -} diff --git a/raphtory/src/db/graph/views/property_filter/mod.rs b/raphtory/src/db/graph/views/property_filter/mod.rs deleted file mode 100644 index a6b5d1813d..0000000000 --- a/raphtory/src/db/graph/views/property_filter/mod.rs +++ /dev/null @@ -1,1348 +0,0 @@ -use crate::core::{ - entities::properties::props::Meta, sort_comparable_props, utils::errors::GraphError, Prop, -}; -use itertools::Itertools; -use raphtory_api::core::storage::arc_str::ArcStr; -use std::{collections::HashSet, fmt, fmt::Display, ops::Deref, sync::Arc}; -use strsim::levenshtein; - -pub mod edge_property_filter; -pub mod exploded_edge_property_filter; -pub(crate) mod internal; -pub mod node_property_filter; - -#[derive(Debug, Clone, Copy)] -pub enum FilterOperator { - Eq, - Ne, - Lt, - Le, - Gt, - Ge, - In, - NotIn, - IsSome, - IsNone, - FuzzySearch { - levenshtein_distance: usize, - prefix_match: bool, - }, -} - -impl Display for FilterOperator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let operator = match self { - FilterOperator::Eq => "==", - FilterOperator::Ne => "!=", - FilterOperator::Lt => "<", - FilterOperator::Le => "<=", - FilterOperator::Gt => ">", - FilterOperator::Ge => ">=", - FilterOperator::In => "IN", - FilterOperator::NotIn => "NOT_IN", - FilterOperator::IsSome => "IS_SOME", - FilterOperator::IsNone => "IS_NONE", - FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - } => { - return write!(f, "FUZZY_SEARCH({},{})", levenshtein_distance, prefix_match); - } - }; - write!(f, "{}", operator) - } -} - -impl FilterOperator { - pub fn is_strictly_numeric_operation(&self) -> bool { - matches!( - self, - FilterOperator::Lt | FilterOperator::Le | FilterOperator::Gt | FilterOperator::Ge - ) - } - - fn operation(&self) -> impl Fn(&T, &T) -> bool - where - T: ?Sized + PartialEq + PartialOrd, - { - match self { - FilterOperator::Eq => T::eq, - FilterOperator::Ne => T::ne, - FilterOperator::Lt => T::lt, - FilterOperator::Le => T::le, - FilterOperator::Gt => T::gt, - FilterOperator::Ge => T::ge, - _ => panic!("Operation not supported for this operator"), - } - } - - pub fn fuzzy_search( - &self, - levenshtein_distance: usize, - prefix_match: bool, - ) -> impl Fn(&str, &str) -> bool { - move |left: &str, right: &str| { - let levenshtein_match = levenshtein(left, right) <= levenshtein_distance; - let prefix_match = prefix_match && right.starts_with(left); - levenshtein_match || prefix_match - } - } - - fn collection_operation(&self) -> impl Fn(&HashSet, &T) -> bool - where - 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), - _ => panic!("Collection operation not supported for this operator"), - } - } - - pub fn apply_to_property(&self, left: &PropertyFilterValue, right: Option<&Prop>) -> bool { - match left { - PropertyFilterValue::None => match self { - FilterOperator::IsSome => right.is_some(), - FilterOperator::IsNone => right.is_none(), - _ => unreachable!(), - }, - PropertyFilterValue::Single(l) => match self { - FilterOperator::Eq - | FilterOperator::Ne - | FilterOperator::Lt - | FilterOperator::Le - | FilterOperator::Gt - | FilterOperator::Ge => right.map_or(false, |r| self.operation()(r, l)), - FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - } => right.map_or(false, |r| match (l, r) { - (Prop::Str(l), Prop::Str(r)) => { - let fuzzy_fn = self.fuzzy_search(*levenshtein_distance, *prefix_match); - fuzzy_fn(l, r) - } - _ => unreachable!(), - }), - _ => unreachable!(), - }, - PropertyFilterValue::Set(l) => match self { - FilterOperator::In | FilterOperator::NotIn => { - right.map_or(false, |r| self.collection_operation()(l, r)) - } - _ => unreachable!(), - }, - } - } - - pub fn apply(&self, left: &FilterValue, right: Option<&str>) -> bool { - match left { - FilterValue::Single(l) => match self { - FilterOperator::Eq | FilterOperator::Ne => { - right.map_or(false, |r| self.operation()(r, l)) - } - FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - } => right.map_or(false, |r| { - let fuzzy_fn = self.fuzzy_search(*levenshtein_distance, *prefix_match); - fuzzy_fn(l, r) - }), - _ => unreachable!(), - }, - FilterValue::Set(l) => match self { - FilterOperator::In | FilterOperator::NotIn => { - right.map_or(false, |r| self.collection_operation()(l, &r.to_string())) - } - _ => unreachable!(), - }, - } - } -} - -#[derive(Debug, Clone)] -pub enum Temporal { - Any, - Latest, -} - -#[derive(Debug, Clone)] -pub enum PropertyRef { - Property(String), - ConstantProperty(String), - TemporalProperty(String, Temporal), -} - -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::ConstantProperty(name) => write!(f, "ConstantProperty({})", name), - PropertyRef::Property(name) => write!(f, "Property({})", name), - } - } -} - -impl PropertyRef { - pub fn name(&self) -> &str { - match self { - PropertyRef::Property(name) - | PropertyRef::ConstantProperty(name) - | PropertyRef::TemporalProperty(name, _) => name, - } - } -} - -#[derive(Debug, Clone)] -pub enum PropertyFilterValue { - None, - Single(Prop), - Set(Arc>), -} - -impl From> for PropertyFilterValue { - fn from(prop: Option) -> Self { - prop.map_or(PropertyFilterValue::None, |v| { - PropertyFilterValue::Single(v) - }) - } -} - -#[derive(Debug, Clone)] -pub struct PropertyFilter { - pub prop_ref: PropertyRef, - pub prop_value: PropertyFilterValue, - pub operator: FilterOperator, -} - -impl Display for PropertyFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let prop_ref_str = match &self.prop_ref { - PropertyRef::Property(name) => format!("{}", name), - PropertyRef::ConstantProperty(name) => format!("const({})", name), - PropertyRef::TemporalProperty(name, Temporal::Any) => format!("temporal_any({})", name), - PropertyRef::TemporalProperty(name, Temporal::Latest) => { - format!("temporal_latest({})", name) - } - }; - - match &self.prop_value { - PropertyFilterValue::None => { - write!(f, "{} {}", prop_ref_str, self.operator) - } - PropertyFilterValue::Single(value) => { - write!(f, "{} {} {}", prop_ref_str, self.operator, value) - } - PropertyFilterValue::Set(values) => { - let sorted_values = sort_comparable_props(values.iter().collect_vec()); - let values_str = sorted_values - .iter() - .map(|v| format!("{}", v)) - .collect::>() - .join(", "); - write!(f, "{} {} [{}]", prop_ref_str, self.operator, values_str) - } - } - } -} - -impl PropertyFilter { - pub fn eq(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Eq, - } - } - - pub fn ne(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Ne, - } - } - - pub fn le(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Le, - } - } - - pub fn ge(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Ge, - } - } - - pub fn lt(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Lt, - } - } - - pub fn gt(prop_ref: PropertyRef, prop_value: impl Into) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(prop_value.into()), - operator: FilterOperator::Gt, - } - } - - pub fn includes(prop_ref: PropertyRef, prop_values: impl IntoIterator) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), - operator: FilterOperator::In, - } - } - - pub fn excludes(prop_ref: PropertyRef, prop_values: impl IntoIterator) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Set(Arc::new(prop_values.into_iter().collect())), - operator: FilterOperator::NotIn, - } - } - - pub fn is_none(prop_ref: PropertyRef) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::None, - operator: FilterOperator::IsNone, - } - } - - pub fn is_some(prop_ref: PropertyRef) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::None, - operator: FilterOperator::IsSome, - } - } - - pub fn fuzzy_search( - prop_ref: PropertyRef, - prop_value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> Self { - Self { - prop_ref, - prop_value: PropertyFilterValue::Single(Prop::Str(ArcStr::from(prop_value.into()))), - operator: FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - }, - } - } - - pub fn resolve_temporal_prop_ids(&self, meta: &Meta) -> Result, GraphError> { - let prop_name = self.prop_ref.name(); - if let PropertyFilterValue::Single(value) = &self.prop_value { - Ok(meta - .temporal_prop_meta() - .get_and_validate(prop_name, value.dtype())?) - } else { - Ok(meta.temporal_prop_meta().get_id(prop_name)) - } - } - - pub fn resolve_constant_prop_ids(&self, meta: &Meta) -> Result, GraphError> { - let prop_name = self.prop_ref.name(); - if let PropertyFilterValue::Single(value) = &self.prop_value { - Ok(meta - .const_prop_meta() - .get_and_validate(prop_name, value.dtype())?) - } else { - Ok(meta.const_prop_meta().get_id(prop_name)) - } - } - - pub fn matches(&self, other: Option<&Prop>) -> bool { - let value = &self.prop_value; - self.operator.apply_to_property(value, other) - } -} - -#[derive(Debug, Clone)] -pub enum FilterValue { - Single(String), - Set(Arc>), -} - -#[derive(Debug, Clone)] -pub struct Filter { - pub field_name: String, - pub field_value: FilterValue, - pub operator: FilterOperator, -} - -impl Display for Filter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.field_value { - FilterValue::Single(value) => { - write!(f, "{} {} {}", self.field_name, self.operator, value) - } - FilterValue::Set(values) => { - let mut sorted_values: Vec<_> = values.iter().collect(); - sorted_values.sort(); - let values_str = sorted_values - .iter() - .map(|v| format!("{}", v)) - .collect::>() - .join(", "); - write!(f, "{} {} [{}]", self.field_name, self.operator, values_str) - } - } - } -} - -impl Filter { - pub fn eq(field_name: impl Into, field_value: impl Into) -> Self { - Self { - field_name: field_name.into(), - field_value: FilterValue::Single(field_value.into()), - operator: FilterOperator::Eq, - } - } - - pub fn ne(field_name: impl Into, field_value: impl Into) -> Self { - Self { - field_name: field_name.into(), - field_value: FilterValue::Single(field_value.into()), - operator: FilterOperator::Ne, - } - } - - pub fn includes( - field_name: impl Into, - field_values: impl IntoIterator, - ) -> Self { - Self { - field_name: field_name.into(), - field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), - operator: FilterOperator::In, - } - } - - pub fn excludes( - field_name: impl Into, - field_values: impl IntoIterator, - ) -> Self { - Self { - field_name: field_name.into(), - field_value: FilterValue::Set(Arc::new(field_values.into_iter().collect())), - operator: FilterOperator::NotIn, - } - } - - pub fn fuzzy_search( - field_name: impl Into, - field_value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> Self { - Self { - field_name: field_name.into(), - field_value: FilterValue::Single(field_value.into()), - operator: FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - }, - } - } - - pub fn matches(&self, node_value: Option<&str>) -> bool { - self.operator.apply(&self.field_value, node_value) - } -} - -#[derive(Debug, Clone)] -pub enum CompositeNodeFilter { - Node(Filter), - Property(PropertyFilter), - And(Vec), - Or(Vec), -} - -impl Display for CompositeNodeFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CompositeNodeFilter::Property(filter) => write!(f, "NODE_PROPERTY({})", filter), - CompositeNodeFilter::Node(filter) => write!(f, "NODE({})", filter), - CompositeNodeFilter::And(filters) => { - let formatted = filters - .iter() - .map(|filter| format!("({})", filter)) - .collect::>() - .join(" AND "); - write!(f, "{}", formatted) - } - CompositeNodeFilter::Or(filters) => { - let formatted = filters - .iter() - .map(|filter| format!("({})", filter)) - .collect::>() - .join(" OR "); - write!(f, "{}", formatted) - } - } - } -} - -#[derive(Debug, Clone)] -pub enum CompositeEdgeFilter { - Edge(Filter), - Property(PropertyFilter), - And(Vec), - Or(Vec), -} - -impl Display for CompositeEdgeFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CompositeEdgeFilter::Property(filter) => write!(f, "EDGE_PROPERTY({})", filter), - CompositeEdgeFilter::Edge(filter) => write!(f, "EDGE({})", filter), - CompositeEdgeFilter::And(filters) => { - let formatted = filters - .iter() - .map(|filter| format!("({})", filter)) - .collect::>() - .join(" AND "); - write!(f, "{}", formatted) - } - CompositeEdgeFilter::Or(filters) => { - let formatted = filters - .iter() - .map(|filter| format!("({})", filter)) - .collect::>() - .join(" OR "); - write!(f, "{}", formatted) - } - } - } -} - -// Fluent Composite Filter Builder APIs -#[derive(Clone, Debug)] -pub enum FilterExpr { - Node(Filter), - Edge(Filter), - Property(PropertyFilter), - And(Vec), - Or(Vec), -} - -impl Display for FilterExpr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl FilterExpr { - pub fn and(self, other: FilterExpr) -> Self { - match self { - FilterExpr::And(mut filters) => { - filters.push(other); - FilterExpr::And(filters) - } - _ => FilterExpr::And(vec![self, other]), - } - } - - pub fn or(self, other: FilterExpr) -> Self { - match self { - FilterExpr::Or(mut filters) => { - filters.push(other); - FilterExpr::Or(filters) - } - _ => FilterExpr::Or(vec![self, other]), - } - } -} - -pub fn resolve_as_node_filter(filter: FilterExpr) -> Result { - match filter { - FilterExpr::Property(prop) => Ok(CompositeNodeFilter::Property(prop)), - FilterExpr::Node(filter) => Ok(CompositeNodeFilter::Node(filter)), - FilterExpr::And(filters) => Ok(CompositeNodeFilter::And( - filters - .into_iter() - .map(resolve_as_node_filter) - .collect::, _>>()?, - )), - FilterExpr::Or(filters) => Ok(CompositeNodeFilter::Or( - filters - .into_iter() - .map(resolve_as_node_filter) - .collect::, _>>()?, - )), - FilterExpr::Edge(_) => Err(GraphError::IllegalFilterExpr( - filter, - "Edge filter cannot be used in node filtering!".to_string(), - )), - } -} - -pub fn resolve_as_edge_filter(filter: FilterExpr) -> Result { - match filter { - FilterExpr::Property(prop) => Ok(CompositeEdgeFilter::Property(prop)), - FilterExpr::Edge(filter) => Ok(CompositeEdgeFilter::Edge(filter)), - FilterExpr::And(filters) => Ok(CompositeEdgeFilter::And( - filters - .into_iter() - .map(resolve_as_edge_filter) - .collect::, _>>()?, - )), - FilterExpr::Or(filters) => Ok(CompositeEdgeFilter::Or( - filters - .into_iter() - .map(resolve_as_edge_filter) - .collect::, _>>()?, - )), - FilterExpr::Node(_) => Err(GraphError::IllegalFilterExpr( - filter, - "Node filter cannot be used in edge filtering!".to_string(), - )), - } -} - -// TODO: This code may go once raphtory APIs start supporting FilterExpr -pub fn resolve_as_property_filter(filter: FilterExpr) -> Result { - match filter { - FilterExpr::Property(prop) => Ok(prop), - _ => Err(GraphError::IllegalFilterExpr( - filter, - "Non-property filter cannot be used in strictly property filtering!".to_string(), - )), - } -} - -pub trait InternalPropertyFilterOps: Send + Sync { - fn property_ref(&self) -> PropertyRef; -} - -impl InternalPropertyFilterOps for Arc { - fn property_ref(&self) -> PropertyRef { - self.deref().property_ref() - } -} - -pub trait PropertyFilterOps { - fn eq(&self, value: impl Into) -> FilterExpr; - - fn ne(&self, value: impl Into) -> FilterExpr; - - fn le(&self, value: impl Into) -> FilterExpr; - - fn ge(&self, value: impl Into) -> FilterExpr; - - fn lt(&self, value: impl Into) -> FilterExpr; - - fn gt(&self, value: impl Into) -> FilterExpr; - - fn includes(&self, values: impl IntoIterator) -> FilterExpr; - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr; - - fn is_none(&self) -> FilterExpr; - - fn is_some(&self) -> FilterExpr; - - fn fuzzy_search( - &self, - prop_value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr; -} - -impl PropertyFilterOps for T { - fn eq(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::eq(self.property_ref(), value.into())) - } - - fn ne(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::ne(self.property_ref(), value.into())) - } - - fn le(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::le(self.property_ref(), value.into())) - } - - fn ge(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::ge(self.property_ref(), value.into())) - } - - fn lt(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::lt(self.property_ref(), value.into())) - } - - fn gt(&self, value: impl Into) -> FilterExpr { - FilterExpr::Property(PropertyFilter::gt(self.property_ref(), value.into())) - } - - fn includes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Property(PropertyFilter::includes( - self.property_ref(), - values.into_iter(), - )) - } - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Property(PropertyFilter::excludes( - self.property_ref(), - values.into_iter(), - )) - } - - fn is_none(&self) -> FilterExpr { - FilterExpr::Property(PropertyFilter::is_none(self.property_ref())) - } - - fn is_some(&self) -> FilterExpr { - FilterExpr::Property(PropertyFilter::is_some(self.property_ref())) - } - - fn fuzzy_search( - &self, - prop_value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr { - FilterExpr::Property(PropertyFilter::fuzzy_search( - self.property_ref(), - prop_value.into(), - levenshtein_distance, - prefix_match, - )) - } -} - -#[derive(Clone)] -pub struct PropertyFilterBuilder(pub String); - -impl PropertyFilterBuilder { - pub fn constant(self) -> ConstPropertyFilterBuilder { - ConstPropertyFilterBuilder(self.0) - } - - pub fn temporal(self) -> TemporalPropertyFilterBuilder { - TemporalPropertyFilterBuilder(self.0) - } -} - -impl InternalPropertyFilterOps for PropertyFilterBuilder { - fn property_ref(&self) -> PropertyRef { - PropertyRef::Property(self.0.clone()) - } -} - -#[derive(Clone)] -pub struct ConstPropertyFilterBuilder(pub String); - -impl InternalPropertyFilterOps for ConstPropertyFilterBuilder { - fn property_ref(&self) -> PropertyRef { - PropertyRef::ConstantProperty(self.0.clone()) - } -} - -#[derive(Clone)] -pub struct AnyTemporalPropertyFilterBuilder(pub String); - -impl InternalPropertyFilterOps for AnyTemporalPropertyFilterBuilder { - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Any) - } -} - -#[derive(Clone)] -pub struct LatestTemporalPropertyFilterBuilder(pub String); - -impl InternalPropertyFilterOps for LatestTemporalPropertyFilterBuilder { - fn property_ref(&self) -> PropertyRef { - PropertyRef::TemporalProperty(self.0.clone(), Temporal::Latest) - } -} - -#[derive(Clone)] -pub struct TemporalPropertyFilterBuilder(pub String); - -impl TemporalPropertyFilterBuilder { - pub fn any(self) -> AnyTemporalPropertyFilterBuilder { - AnyTemporalPropertyFilterBuilder(self.0) - } - - pub fn latest(self) -> LatestTemporalPropertyFilterBuilder { - LatestTemporalPropertyFilterBuilder(self.0) - } -} - -impl PropertyFilter { - pub fn property(name: impl AsRef) -> PropertyFilterBuilder { - PropertyFilterBuilder(name.as_ref().to_string()) - } -} - -pub trait InternalNodeFilterOps: Send + Sync { - fn field_name(&self) -> &'static str; -} - -impl InternalNodeFilterOps for Arc { - fn field_name(&self) -> &'static str { - self.deref().field_name() - } -} - -pub trait NodeFilterOps { - fn eq(&self, value: impl Into) -> FilterExpr; - - fn ne(&self, value: impl Into) -> FilterExpr; - - fn includes(&self, values: impl IntoIterator) -> FilterExpr; - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr; - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr; -} - -impl NodeFilterOps for T { - fn eq(&self, value: impl Into) -> FilterExpr { - FilterExpr::Node(Filter::eq(self.field_name(), value)) - } - - fn ne(&self, value: impl Into) -> FilterExpr { - FilterExpr::Node(Filter::ne(self.field_name(), value)) - } - - fn includes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Node(Filter::includes(self.field_name(), values)) - } - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Node(Filter::excludes(self.field_name(), values)) - } - - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr { - FilterExpr::Node(Filter::fuzzy_search( - self.field_name(), - value, - levenshtein_distance, - prefix_match, - )) - } -} - -pub struct NodeNameFilterBuilder; - -impl InternalNodeFilterOps for NodeNameFilterBuilder { - fn field_name(&self) -> &'static str { - "node_name" - } -} - -pub struct NodeTypeFilterBuilder; - -impl InternalNodeFilterOps for NodeTypeFilterBuilder { - fn field_name(&self) -> &'static str { - "node_type" - } -} - -#[derive(Clone)] -pub struct NodeFilter; - -impl NodeFilter { - pub fn node_name() -> NodeNameFilterBuilder { - NodeNameFilterBuilder - } - - pub fn node_type() -> NodeTypeFilterBuilder { - NodeTypeFilterBuilder - } -} - -pub trait InternalEdgeFilterOps: Send + Sync { - fn field_name(&self) -> &'static str; -} - -impl InternalEdgeFilterOps for Arc { - fn field_name(&self) -> &'static str { - self.deref().field_name() - } -} - -pub trait EdgeFilterOps { - fn eq(&self, value: impl Into) -> FilterExpr; - - fn ne(&self, value: impl Into) -> FilterExpr; - - fn includes(&self, values: impl IntoIterator) -> FilterExpr; - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr; - - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr; -} - -impl EdgeFilterOps for T { - fn eq(&self, value: impl Into) -> FilterExpr { - FilterExpr::Edge(Filter::eq(self.field_name(), value)) - } - - fn ne(&self, value: impl Into) -> FilterExpr { - FilterExpr::Edge(Filter::ne(self.field_name(), value)) - } - - fn includes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Edge(Filter::includes(self.field_name(), values)) - } - - fn excludes(&self, values: impl IntoIterator) -> FilterExpr { - FilterExpr::Edge(Filter::excludes(self.field_name(), values)) - } - - fn fuzzy_search( - &self, - value: impl Into, - levenshtein_distance: usize, - prefix_match: bool, - ) -> FilterExpr { - FilterExpr::Edge(Filter::fuzzy_search( - self.field_name(), - value, - levenshtein_distance, - prefix_match, - )) - } -} - -pub struct EdgeSourceFilterBuilder; - -impl InternalEdgeFilterOps for EdgeSourceFilterBuilder { - fn field_name(&self) -> &'static str { - "src" - } -} - -pub struct EdgeDestinationFilterBuilder; - -impl InternalEdgeFilterOps for EdgeDestinationFilterBuilder { - fn field_name(&self) -> &'static str { - "dst" - } -} - -#[derive(Clone)] -pub struct EdgeFilter; - -impl EdgeFilter { - pub fn src() -> EdgeSourceFilterBuilder { - EdgeSourceFilterBuilder - } - - pub fn dst() -> EdgeDestinationFilterBuilder { - EdgeDestinationFilterBuilder - } -} - -#[cfg(test)] -mod test_fluent_builder_apis { - use super::*; - use PropertyFilter; - - #[test] - fn test_node_property_filter_build() { - let filter_expr = PropertyFilter::property("p").eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p".to_string()), - "raphtory", - )); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_const_property_filter_build() { - let filter_expr = PropertyFilter::property("p").constant().eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::ConstantProperty("p".to_string()), - "raphtory", - )); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_any_temporal_property_filter_build() { - let filter_expr = PropertyFilter::property("p") - .temporal() - .any() - .eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p".to_string(), Temporal::Any), - "raphtory", - )); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_latest_temporal_property_filter_build() { - let filter_expr = PropertyFilter::property("p") - .temporal() - .latest() - .eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p".to_string(), Temporal::Latest), - "raphtory", - )); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_name_filter_build() { - let filter_expr = NodeFilter::node_name().eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Node(Filter::eq("node_name", "raphtory")); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_type_filter_build() { - let filter_expr = NodeFilter::node_type().eq("raphtory"); - let node_property_filter = resolve_as_node_filter(filter_expr).unwrap(); - let node_property_filter2 = CompositeNodeFilter::Node(Filter::eq("node_type", "raphtory")); - assert_eq!( - node_property_filter.to_string(), - node_property_filter2.to_string() - ); - } - - #[test] - fn test_node_filter_composition() { - let filter_expr = NodeFilter::node_name() - .eq("fire_nation") - .and(PropertyFilter::property("p2").constant().eq(2u64)) - .and(PropertyFilter::property("p1").eq(1u64)) - .and( - PropertyFilter::property("p3") - .temporal() - .any() - .eq(5u64) - .or(PropertyFilter::property("p4").temporal().latest().eq(7u64)), - ) - .or(NodeFilter::node_type().eq("raphtory")) - .or(PropertyFilter::property("p5").eq(9u64)); - let node_composite_filter = resolve_as_node_filter(filter_expr).unwrap(); - - let node_composite_filter2 = CompositeNodeFilter::Or(vec![ - CompositeNodeFilter::And(vec![ - CompositeNodeFilter::Node(Filter::eq("node_name", "fire_nation")), - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::ConstantProperty("p2".to_string()), - 2u64, - )), - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p1".to_string()), - 1u64, - )), - CompositeNodeFilter::Or(vec![ - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p3".to_string(), Temporal::Any), - 5u64, - )), - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p4".to_string(), Temporal::Latest), - 7u64, - )), - ]), - ]), - CompositeNodeFilter::Node(Filter::eq("node_type", "raphtory")), - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p5".to_string()), - 9u64, - )), - ]); - - assert_eq!( - node_composite_filter.to_string(), - node_composite_filter2.to_string() - ); - } - - #[test] - fn test_edge_src_filter_build() { - let filter_expr = EdgeFilter::src().eq("raphtory"); - let edge_property_filter = resolve_as_edge_filter(filter_expr).unwrap(); - let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory")); - assert_eq!( - edge_property_filter.to_string(), - edge_property_filter2.to_string() - ); - } - - #[test] - fn test_edge_dst_filter_build() { - let filter_expr = EdgeFilter::dst().eq("raphtory"); - let edge_property_filter = resolve_as_edge_filter(filter_expr).unwrap(); - let edge_property_filter2 = CompositeEdgeFilter::Edge(Filter::eq("dst", "raphtory")); - assert_eq!( - edge_property_filter.to_string(), - edge_property_filter2.to_string() - ); - } - - #[test] - fn test_edge_filter_composition() { - let filter_expr = EdgeFilter::src() - .eq("fire_nation") - .and(PropertyFilter::property("p2").constant().eq(2u64)) - .and(PropertyFilter::property("p1").eq(1u64)) - .and( - PropertyFilter::property("p3") - .temporal() - .any() - .eq(5u64) - .or(PropertyFilter::property("p4").temporal().latest().eq(7u64)), - ) - .or(EdgeFilter::src().eq("raphtory")) - .or(PropertyFilter::property("p5").eq(9u64)); - let edge_composite_filter = resolve_as_edge_filter(filter_expr).unwrap(); - - let edge_composite_filter2 = CompositeEdgeFilter::Or(vec![ - CompositeEdgeFilter::And(vec![ - CompositeEdgeFilter::Edge(Filter::eq("src", "fire_nation")), - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::ConstantProperty("p2".to_string()), - 2u64, - )), - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p1".to_string()), - 1u64, - )), - CompositeEdgeFilter::Or(vec![ - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p3".to_string(), Temporal::Any), - 5u64, - )), - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::TemporalProperty("p4".to_string(), Temporal::Latest), - 7u64, - )), - ]), - ]), - CompositeEdgeFilter::Edge(Filter::eq("src", "raphtory")), - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p5".to_string()), - 9u64, - )), - ]); - - assert_eq!( - edge_composite_filter.to_string(), - edge_composite_filter2.to_string() - ); - } -} - -// TODO: Add tests for const and temporal properties -#[cfg(test)] -mod test_composite_filters { - use crate::{ - core::Prop, - db::graph::views::property_filter::{ - CompositeEdgeFilter, CompositeNodeFilter, Filter, PropertyRef, - }, - prelude::PropertyFilter, - }; - use raphtory_api::core::storage::arc_str::ArcStr; - - #[test] - fn test_composite_node_filter() { - assert_eq!( - "NODE_PROPERTY(p2 == 2)", - CompositeNodeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p2".to_string()), - 2u64 - )) - .to_string() - ); - - assert_eq!( - "((NODE(node_type NOT_IN [fire_nation, water_tribe])) AND (NODE_PROPERTY(p2 == 2)) AND (NODE_PROPERTY(p1 == 1)) AND ((NODE_PROPERTY(p3 <= 5)) OR (NODE_PROPERTY(p4 IN [2, 10])))) OR (NODE(node_name == pometry)) OR (NODE_PROPERTY(p5 == 9))", - CompositeNodeFilter::Or(vec![ - CompositeNodeFilter::And(vec![ - CompositeNodeFilter::Node(Filter::excludes( - "node_type", - vec!["fire_nation".into(), "water_tribe".into()] - )), - CompositeNodeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p2".to_string()), 2u64)), - CompositeNodeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p1".to_string()), 1u64)), - CompositeNodeFilter::Or(vec![ - CompositeNodeFilter::Property(PropertyFilter::le(PropertyRef::Property("p3".to_string()), 5u64)), - CompositeNodeFilter::Property(PropertyFilter::includes(PropertyRef::Property("p4".to_string()), vec![Prop::U64(10), Prop::U64(2)])) - ]), - ]), - CompositeNodeFilter::Node(Filter::eq("node_name", "pometry")), - CompositeNodeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p5".to_string()), 9u64)), - ]) - .to_string() - ); - - assert_eq!( - "(NODE(name FUZZY_SEARCH(1,true) shivam)) AND (NODE_PROPERTY(nation FUZZY_SEARCH(1,false) air_nomad))", - CompositeNodeFilter::And(vec![ - CompositeNodeFilter::Node(Filter::fuzzy_search("name", "shivam", 1, true)), - CompositeNodeFilter::Property(PropertyFilter::fuzzy_search( - PropertyRef::Property("nation".to_string()), - "air_nomad", - 1, - false, - )), - ]) - .to_string() - ); - } - - #[test] - fn test_composite_edge_filter() { - assert_eq!( - "EDGE_PROPERTY(p2 == 2)", - CompositeEdgeFilter::Property(PropertyFilter::eq( - PropertyRef::Property("p2".to_string()), - 2u64 - )) - .to_string() - ); - - assert_eq!( - "((EDGE(edge_type NOT_IN [fire_nation, water_tribe])) AND (EDGE_PROPERTY(p2 == 2)) AND (EDGE_PROPERTY(p1 == 1)) AND ((EDGE_PROPERTY(p3 <= 5)) OR (EDGE_PROPERTY(p4 IN [2, 10])))) OR (EDGE(src == pometry)) OR (EDGE_PROPERTY(p5 == 9))", - CompositeEdgeFilter::Or(vec![ - CompositeEdgeFilter::And(vec![ - CompositeEdgeFilter::Edge(Filter::excludes( - "edge_type", - vec!["fire_nation".into(), "water_tribe".into()] - )), - CompositeEdgeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p2".to_string()), 2u64)), - CompositeEdgeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p1".to_string()), 1u64)), - CompositeEdgeFilter::Or(vec![ - CompositeEdgeFilter::Property(PropertyFilter::le(PropertyRef::Property("p3".to_string()), 5u64)), - CompositeEdgeFilter::Property(PropertyFilter::includes(PropertyRef::Property("p4".to_string()), vec![Prop::U64(10), Prop::U64(2)])) - ]), - ]), - CompositeEdgeFilter::Edge(Filter::eq("src", "pometry")), - CompositeEdgeFilter::Property(PropertyFilter::eq(PropertyRef::Property("p5".to_string()), 9u64)), - ]) - .to_string() - ); - - assert_eq!( - "(EDGE(name FUZZY_SEARCH(1,true) shivam)) AND (EDGE_PROPERTY(nation FUZZY_SEARCH(1,false) air_nomad))", - CompositeEdgeFilter::And(vec![ - CompositeEdgeFilter::Edge(Filter::fuzzy_search("name", "shivam", 1, true)), - CompositeEdgeFilter::Property(PropertyFilter::fuzzy_search( - PropertyRef::Property("nation".to_string()), - "air_nomad", - 1, - false, - )), - ]) - .to_string() - ); - } - - #[test] - fn test_fuzzy_search() { - let filter = Filter::fuzzy_search("name", "pomet", 2, false); - assert!(filter.matches(Some("pometry"))); - - let filter = Filter::fuzzy_search("name", "shivam_kapoor", 2, false); - assert!(filter.matches(Some("shivam_kapoor2"))); - - let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); - assert!(filter.matches(Some("shivam_kapoor2"))); - - let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); - assert!(filter.matches(Some("shivam_kapoor2"))); - - let filter = Filter::fuzzy_search("name", "shivam kapoor", 2, false); - assert!(!filter.matches(Some("shivam1_kapoor2"))); - } - - #[test] - fn test_fuzzy_search_prefix_match() { - let filter = Filter::fuzzy_search("name", "pome", 2, false); - assert!(!filter.matches(Some("pometry"))); - - let filter = Filter::fuzzy_search("name", "pome", 2, true); - assert!(filter.matches(Some("pometry"))); - } - - #[test] - fn test_fuzzy_search_property() { - let filter = PropertyFilter::fuzzy_search( - PropertyRef::Property("prop".to_string()), - "pomet", - 2, - false, - ); - assert!(filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); - } - - #[test] - fn test_fuzzy_search_property_prefix_match() { - let filter = PropertyFilter::fuzzy_search( - PropertyRef::Property("prop".to_string()), - "pome", - 2, - false, - ); - assert!(!filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); - - let filter = PropertyFilter::fuzzy_search( - PropertyRef::Property("prop".to_string()), - "pome", - 2, - true, - ); - assert!(filter.matches(Some(&Prop::Str(ArcStr::from("pometry"))))); - } -} diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 677cb17dd6..33567a56f6 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -75,7 +75,6 @@ use std::{ fmt::{Debug, Formatter}, iter, ops::Range, - sync::Arc, }; use crate::db::api::view::internal::InheritStorageOps; @@ -261,7 +260,7 @@ impl<'graph, G: GraphViewOps<'graph>> ListOps for WindowedGraph { fn node_list(&self) -> NodeList { if self.window_is_empty() { NodeList::List { - nodes: Index::default(), + elems: Index::default(), } } else { self.graph.node_list() @@ -271,7 +270,7 @@ impl<'graph, G: GraphViewOps<'graph>> ListOps for WindowedGraph { fn edge_list(&self) -> EdgeList { if self.window_is_empty() { EdgeList::List { - edges: Arc::new([]), + elems: Index::default(), } } else { self.graph.edge_list() @@ -1451,1962 +1450,2081 @@ mod views_test { }); } - #[cfg(all(test, feature = "search"))] - mod search_nodes_window_graph_tests { - use crate::{ - core::Prop, - db::{ - api::{ - mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, - view::{SearchableGraphOps, StaticGraphViewOps}, + mod test_filters_window_graph { + mod test_nodes_filters_window_graph { + use crate::{ + assert_filter_nodes_results_pg_w, assert_filter_nodes_results_w, + assert_filter_results_w, assert_search_nodes_results_pg_w, + assert_search_nodes_results_w, assert_search_results_w, + core::Prop, + db::{ + api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::StaticGraphViewOps, + }, + graph::views::{ + deletion_graph::PersistentGraph, + filter::model::{ + ComposableFilter, NodeFilter, NodeFilterBuilderOps, PropertyFilterOps, + }, + }, }, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{FilterExpr, NodeFilter, NodeFilterOps, PropertyFilterOps}, - }, - }, - prelude::{ - AdditionOps, Graph, NodeViewOps, PropertyAdditionOps, PropertyFilter, TimeOps, - }, - }; - use raphtory_api::core::storage::arc_str::ArcStr; - use std::ops::Range; - - fn init_graph< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - let nodes = vec![ - ( - 6, - "N1", - vec![ - ("p1", Prop::U64(2u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - Some("air_nomad"), - ), - ( - 7, - "N1", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(5i64)), - ("k3", Prop::Bool(false)), - ], - Some("air_nomad"), - ), - ( - 6, - "N2", - vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(6.0f64))], - Some("water_tribe"), - ), - ( - 7, - "N2", - vec![ - ("p1", Prop::U64(2u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - 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)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - Some("air_nomad"), - ), - ( - 6, - "N5", - vec![ - ("p1", Prop::U64(2u64)), - ("k2", Prop::Str(ArcStr::from("Pometry"))), - ("k4", Prop::F64(1.0f64)), - ], - Some("air_nomad"), - ), - (5, "N6", vec![("p1", Prop::U64(1u64))], Some("fire_nation")), - ( - 6, - "N6", - vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(1.0f64))], - Some("fire_nation"), - ), - ( - 3, - "N7", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - 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)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - Some("fire_nation"), - ), - (2, "N9", vec![("p1", Prop::U64(2u64))], None), - (2, "N10", vec![("q1", Prop::U64(0u64))], None), - (2, "N10", vec![("p1", Prop::U64(3u64))], None), - (2, "N11", vec![("p1", Prop::U64(3u64))], None), - (2, "N11", vec![("q1", Prop::U64(0u64))], None), - (2, "N12", vec![("q1", Prop::U64(0u64))], None), - ( - 3, - "N12", - vec![ - ("p1", Prop::U64(3u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - None, - ), - (2, "N13", vec![("q1", Prop::U64(0u64))], None), - (3, "N13", vec![("p1", Prop::U64(3u64))], None), - (2, "N14", vec![("q1", Prop::U64(0u64))], None), - (2, "N15", vec![], None), - ]; - - // Add nodes to the graph - for (id, name, props, layer) in &nodes { - graph.add_node(*id, name, props.clone(), *layer).unwrap(); - } - - // Constant property assignments - let constant_properties = vec![ - ( - "N1", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(3i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - ), - ("N4", vec![("p1", Prop::U64(2u64))]), - ("N9", vec![("p1", Prop::U64(1u64))]), - ("N10", vec![("p1", Prop::U64(1u64))]), - ("N11", vec![("p1", Prop::U64(1u64))]), - ("N12", vec![("p1", Prop::U64(1u64))]), - ( - "N13", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - ), - ("N14", vec![("p1", Prop::U64(1u64))]), - ("N15", vec![("p1", Prop::U64(1u64))]), - ]; - - // Apply constant properties - for (node, props) in constant_properties { + prelude::{AdditionOps, Graph, PropertyAdditionOps, PropertyFilter, TimeOps}, + }; + use raphtory_api::core::storage::arc_str::ArcStr; + use std::{ops::Range, sync::Arc}; + + use crate::db::graph::views::{ + filter::internal::InternalNodeFilterOps, test_helpers::filter_nodes_with, + }; + + #[cfg(feature = "storage")] + use tempfile::TempDir; + + #[cfg(feature = "storage")] + use crate::disk_graph::DiskGraphStorage; + + #[cfg(feature = "search")] + use crate::db::graph::views::test_helpers::search_nodes_with; + + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let nodes = vec![ + ( + 6, + "N1", + vec![ + ("p1", Prop::U64(2u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + Some("air_nomad"), + ), + ( + 7, + "N1", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(5i64)), + ("k3", Prop::Bool(false)), + ], + Some("air_nomad"), + ), + ( + 6, + "N2", + vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(6.0f64))], + Some("water_tribe"), + ), + ( + 7, + "N2", + vec![ + ("p1", Prop::U64(2u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + 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)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + Some("air_nomad"), + ), + ( + 6, + "N5", + vec![ + ("p1", Prop::U64(2u64)), + ("k2", Prop::Str(ArcStr::from("Pometry"))), + ("k4", Prop::F64(1.0f64)), + ], + Some("air_nomad"), + ), + (5, "N6", vec![("p1", Prop::U64(1u64))], Some("fire_nation")), + ( + 6, + "N6", + vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(1.0f64))], + Some("fire_nation"), + ), + ( + 3, + "N7", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + 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)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + Some("fire_nation"), + ), + (2, "N9", vec![("p1", Prop::U64(2u64))], None), + (2, "N10", vec![("q1", Prop::U64(0u64))], None), + (2, "N10", vec![("p1", Prop::U64(3u64))], None), + (2, "N11", vec![("p1", Prop::U64(3u64))], None), + (2, "N11", vec![("q1", Prop::U64(0u64))], None), + (2, "N12", vec![("q1", Prop::U64(0u64))], None), + ( + 3, + "N12", + vec![ + ("p1", Prop::U64(3u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + None, + ), + (2, "N13", vec![("q1", Prop::U64(0u64))], None), + (3, "N13", vec![("p1", Prop::U64(3u64))], None), + ( + 2, + "N14", + vec![ + ("q1", Prop::U64(0u64)), + ( + "x", + Prop::List(Arc::from(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ])), + ), + ], + None, + ), + (2, "N15", vec![], None), + ]; + + // Add nodes to the graph + for (id, name, props, layer) in &nodes { + graph.add_node(*id, name, props.clone(), *layer).unwrap(); + } + + // Constant property assignments + let constant_properties = vec![ + ( + "N1", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(3i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + ), + ("N4", vec![("p1", Prop::U64(2u64))]), + ("N9", vec![("p1", Prop::U64(1u64))]), + ("N10", vec![("p1", Prop::U64(1u64))]), + ("N11", vec![("p1", Prop::U64(1u64))]), + ("N12", vec![("p1", Prop::U64(1u64))]), + ( + "N13", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + ), + ("N14", vec![("p1", Prop::U64(1u64))]), + ("N15", vec![("p1", Prop::U64(1u64))]), + ]; + + // Apply constant properties + for (node, props) in constant_properties { + graph + .node(node) + .unwrap() + .add_constant_properties(props) + .unwrap(); + } + graph - .node(node) - .unwrap() - .add_constant_properties(props) - .unwrap(); } - graph - } + fn filter_nodes_w(filter: I, w: Range) -> Vec { + filter_nodes_with(filter, init_graph(Graph::new()).window(w.start, w.end)) + } - fn search_nodes< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - w: Range, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .window(w.start, w.end) - .search_nodes(filter, 20, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - results - } + fn filter_nodes_pg_w( + filter: I, + w: Range, + ) -> Vec { + filter_nodes_with( + filter, + init_graph(PersistentGraph::new()).window(w.start, w.end), + ) + } - fn search_nodes_for_node_name_eq(constructor: F) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_name().eq("N2"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N2"]); - } + #[cfg(feature = "search")] + mod search_nodes { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::filter::model::AsNodeFilter; + use crate::db::graph::views::test_helpers::search_nodes_with; + use crate::db::graph::views::window_graph::views_test::test_filters_window_graph::test_nodes_filters_window_graph::init_graph; + use crate::prelude::{Graph, TimeOps}; + + pub fn search_nodes_w(filter: I, w: Range) -> Vec { + search_nodes_with(filter, init_graph(Graph::new()).window(w.start, w.end)) + } + + pub fn search_nodes_pg_w(filter: I, w: Range) -> Vec { + search_nodes_with( + filter, + init_graph(PersistentGraph::new()).window(w.start, w.end), + ) + } + } - #[test] - fn test_search_nodes_graph_for_node_name_eq() { - search_nodes_for_node_name_eq(Graph::new); - } + #[cfg(feature = "search")] + use search_nodes::*; - #[test] - fn test_search_nodes_persistent_graph_for_node_name_eq() { - search_nodes_for_node_name_eq(PersistentGraph::new); - } + #[test] + fn test_nodes_filters_for_node_name_eq() { + let filter = NodeFilter::name().eq("N2"); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_node_name_ne(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_name().ne("N2"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_nodes_filters_pg_for_node_name_eq() { + let filter = NodeFilter::name().eq("N2"); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_node_name_ne() { - search_nodes_for_node_name_ne(Graph::new, vec!["N1", "N3", "N5", "N6"]); - } + #[test] + fn test_nodes_filters_for_node_name_ne() { + let filter = NodeFilter::name().ne("N2"); + let expected_results = vec!["N1", "N3", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_name_ne() { - search_nodes_for_node_name_ne( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_node_name_ne() { + let filter = NodeFilter::name().ne("N2"); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N3", "N5", "N6", "N7", "N8", "N9", - ], - ); - } - - fn search_nodes_for_node_name_in(constructor: F) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_name().includes(vec!["N2".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N2"]); - - let filter = NodeFilter::node_name().includes(vec!["N2".into(), "N5".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N2", "N5"]); - } - - #[test] - fn test_search_nodes_graph_for_node_name_in() { - search_nodes_for_node_name_in(Graph::new); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_name_in() { - search_nodes_for_node_name_in(PersistentGraph::new); - } + #[test] + fn test_nodes_filters_for_node_name_in() { + let filter = NodeFilter::name().is_in(vec!["N2".into()]); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = NodeFilter::name().is_in(vec!["N2".into(), "N5".into()]); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_node_name_not_in(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_name().excludes(vec!["N5".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_nodes_filters_pg_for_node_name_in() { + let filter = NodeFilter::name().is_in(vec!["N2".into()]); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = NodeFilter::name().is_in(vec!["N2".into(), "N5".into()]); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_node_name_not_in() { - search_nodes_for_node_name_not_in(Graph::new, vec!["N1", "N2", "N3", "N6"]); - } + #[test] + fn test_nodes_filters_for_node_name_not_in() { + let filter = NodeFilter::name().is_not_in(vec!["N5".into()]); + let expected_results = vec!["N1", "N2", "N3", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_name_not_in() { - search_nodes_for_node_name_not_in( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_node_name_not_in() { + let filter = NodeFilter::name().is_not_in(vec!["N5".into()]); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N6", "N7", "N8", "N9", - ], - ); - } - - fn search_nodes_for_node_type_eq(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_type().eq("fire_nation"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } - - #[test] - fn test_search_nodes_graph_for_node_type_eq() { - search_nodes_for_node_type_eq(Graph::new, vec!["N6"]); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_type_eq() { - search_nodes_for_node_type_eq(PersistentGraph::new, vec!["N6", "N8"]); - } + #[test] + fn test_nodes_filters_for_node_type_eq() { + let filter = NodeFilter::node_type().eq("fire_nation"); + let expected_results = vec!["N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_node_type_ne(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_type().ne("fire_nation"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_nodes_filters_pg_for_node_type_eq() { + let filter = NodeFilter::node_type().eq("fire_nation"); + let expected_results = vec!["N6", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_node_type_ne() { - search_nodes_for_node_type_ne(Graph::new, vec!["N1", "N2", "N3", "N5"]); - } + #[test] + fn test_nodes_filters_for_node_type_ne() { + let filter = NodeFilter::node_type().ne("fire_nation"); + let expected_results = vec!["N1", "N2", "N3", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_type_ne() { - search_nodes_for_node_type_ne( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_node_type_ne() { + let filter = NodeFilter::node_type().ne("fire_nation"); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N5", "N7", "N9", - ], - ); - } - - fn search_nodes_for_node_type_in( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_type().includes(vec!["fire_nation".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = - NodeFilter::node_type().includes(vec!["fire_nation".into(), "air_nomads".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - } - - #[test] - fn test_search_nodes_graph_for_node_type_in() { - search_nodes_for_node_type_in(Graph::new, vec!["N6"], vec!["N1", "N3", "N5", "N6"]); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_type_in() { - search_nodes_for_node_type_in( - PersistentGraph::new, - vec!["N6", "N8"], - vec!["N1", "N3", "N5", "N6", "N7", "N8"], - ); - } + #[test] + fn test_nodes_filters_for_node_type_in() { + let filter = NodeFilter::node_type().is_in(vec!["fire_nation".into()]); + let expected_results = vec!["N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = + NodeFilter::node_type().is_in(vec!["fire_nation".into(), "air_nomad".into()]); + let expected_results = vec!["N1", "N3", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_node_type_not_in(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = NodeFilter::node_type().excludes(vec!["fire_nation".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_nodes_filters_pg_for_node_type_in() { + let filter = NodeFilter::node_type().is_in(vec!["fire_nation".into()]); + let expected_results = vec!["N6", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = + NodeFilter::node_type().is_in(vec!["fire_nation".into(), "air_nomad".into()]); + let expected_results = vec!["N1", "N3", "N5", "N6", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_node_type_not_in() { - search_nodes_for_node_type_not_in(Graph::new, vec!["N1", "N2", "N3", "N5"]); - } + #[test] + fn test_nodes_filters_for_node_type_not_in() { + let filter = NodeFilter::node_type().is_not_in(vec!["fire_nation".into()]); + let expected_results = vec!["N1", "N2", "N3", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_node_type_not_in() { - search_nodes_for_node_type_not_in( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_node_type_not_in() { + let filter = NodeFilter::node_type().is_not_in(vec!["fire_nation".into()]); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N5", "N7", "N9", - ], - ); - } - - fn search_nodes_for_property_eq( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").eq(2i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").eq(true); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").eq(6.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } - - #[test] - fn test_search_nodes_graph_for_property_eq() { - search_nodes_for_property_eq( - Graph::new, - vec!["N1", "N3", "N6"], - vec!["N2"], - vec!["N1"], - vec!["N2"], - vec!["N1"], - ); - } - - #[test] - fn test_search_nodes_persistent_graph_for_property_eq() { - search_nodes_for_property_eq( - PersistentGraph::new, - vec!["N1", "N14", "N15", "N3", "N6", "N7"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1"], - ); - } - - fn search_nodes_for_property_ne( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").ne(1u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").ne(2i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").ne(true); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").ne(6.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } - - #[test] - fn test_search_nodes_graph_for_property_ne() { - search_nodes_for_property_ne( - Graph::new, - vec!["N2", "N5"], - vec!["N1"], - vec!["N5"], - vec!["N1"], - vec!["N2", "N5", "N6"], - ); - } - - #[test] - fn test_search_nodes_persistent_graph_for_property_ne() { - search_nodes_for_property_ne( - PersistentGraph::new, - vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"], - vec!["N1"], - vec!["N12", "N13", "N5", "N8"], - vec!["N1"], - vec!["N12", "N13", "N2", "N5", "N6", "N7", "N8"], - ); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_property_lt( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").lt(3u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").lt(3i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").lt(10.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + #[test] + fn test_nodes_filters_for_property_eq() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").eq(2i64); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").eq(true); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").eq(6.0f64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").eq(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_lt() { - search_nodes_for_property_lt( - Graph::new, - vec!["N1", "N2", "N3", "N5", "N6"], - vec!["N2"], - vec!["N1", "N5", "N6"], - ); - } + #[test] + fn test_nodes_filters_pg_for_property_eq() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1", "N14", "N15", "N3", "N6", "N7"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").eq(2i64); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").eq(true); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").eq(6.0f64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").eq(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_lt() { - search_nodes_for_property_lt( - PersistentGraph::new, - vec!["N1", "N14", "N15", "N2", "N3", "N5", "N6", "N7", "N8", "N9"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1", "N5", "N6"], - ); - } + #[test] + fn test_nodes_filters_for_property_ne() { + let filter = PropertyFilter::property("p1").ne(1u64); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ne(2i64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").ne(true); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ne(6.0f64); + let expected_results = vec!["N2", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ne(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = Vec::::new(); + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - fn search_nodes_for_property_le( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").le(1u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").le(2i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").le(6.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + #[test] + fn test_nodes_filters_pg_for_property_ne() { + let filter = PropertyFilter::property("p1").ne(1u64); + let expected_results = vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ne(2i64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").ne(true); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ne(6.0f64); + let expected_results = vec!["N12", "N13", "N2", "N5", "N6", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ne(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = Vec::::new(); + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_le() { - search_nodes_for_property_le( - Graph::new, - vec!["N1", "N3", "N6"], - vec!["N2"], - vec!["N1", "N5", "N6"], - ); - } + #[test] + fn test_nodes_filters_for_property_lt() { + let filter = PropertyFilter::property("p1").lt(3u64); + let expected_results = vec!["N1", "N2", "N3", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").lt(3i64); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").lt(10.0f64); + let expected_results = vec!["N1", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").lt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(7), + Prop::U64(0), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_le() { - search_nodes_for_property_le( - PersistentGraph::new, - vec!["N1", "N14", "N15", "N3", "N6", "N7"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1", "N5", "N6"], - ); - } + #[test] + fn test_nodes_filters_pg_for_property_lt() { + let filter = PropertyFilter::property("p1").lt(3u64); + let expected_results = + vec!["N1", "N14", "N15", "N2", "N3", "N5", "N6", "N7", "N8", "N9"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").lt(3i64); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").lt(10.0f64); + let expected_results = vec!["N1", "N5", "N6"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").lt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(7), + Prop::U64(0), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - fn search_nodes_for_property_gt( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").gt(1u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").gt(2i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").gt(6.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + #[test] + fn test_nodes_filters_for_property_le() { + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N1", "N3", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").le(2i64); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").le(6.0f64); + let expected_results = vec!["N1", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").le(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(7), + Prop::U64(0), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_gt() { - search_nodes_for_property_gt(Graph::new, vec!["N2", "N5"], vec!["N1"], vec!["N2"]); - } + #[test] + fn test_nodes_filters_pg_for_property_le() { + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N1", "N14", "N15", "N3", "N6", "N7"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").le(2i64); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").le(6.0f64); + let expected_results = vec!["N1", "N5", "N6"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").le(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(2), + Prop::U64(3), + ]))); + let expected_results = Vec::::new(); + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_gt() { - search_nodes_for_property_gt( - PersistentGraph::new, - vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"], - vec!["N1"], - vec!["N12", "N13", "N2", "N7", "N8"], - ); - } + #[test] + fn test_nodes_filters_for_property_gt() { + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").gt(2i64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").gt(6.0f64); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").gt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = Vec::::new(); + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - fn search_nodes_for_property_ge( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").ge(1u64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").ge(2i64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").ge(6.0f64); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + #[test] + fn test_nodes_filters_pg_for_property_gt() { + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").gt(2i64); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").gt(6.0f64); + let expected_results = vec!["N12", "N13", "N2", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").gt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(2), + Prop::U64(3), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_ge() { - search_nodes_for_property_ge( - Graph::new, - vec!["N1", "N2", "N3", "N5", "N6"], - vec!["N1", "N2"], - vec!["N1", "N2"], - ); - } + #[test] + fn test_nodes_filters_for_property_ge() { + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec!["N1", "N2", "N3", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ge(2i64); + let expected_results = vec!["N1", "N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ge(6.0f64); + let expected_results = vec!["N1", "N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ge(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_ge() { - search_nodes_for_property_ge( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_property_ge() { + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N5", "N6", "N7", "N8", "N9", - ], - vec!["N1", "N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1", "N12", "N13", "N2", "N7", "N8"], - ); - } - - fn search_nodes_for_property_in( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").includes(vec![2u64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").includes(vec![2i64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").includes(vec!["Paper_Airplane".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").includes(vec![true.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").includes(vec![6.0f64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ge(2i64); + let expected_results = vec!["N1", "N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ge(6.0f64); + let expected_results = vec!["N1", "N12", "N13", "N2", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ge(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(2), + Prop::U64(3), + ]))); + let expected_results = vec!["N14"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_nodes_results_pg_w!(init_graph, filter, 1..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_in() { - search_nodes_for_property_in( - Graph::new, - vec!["N2", "N5"], - vec!["N2"], - vec!["N1", "N2"], - vec!["N2"], - vec!["N1"], - ); - } + #[test] + fn test_nodes_filters_for_property_in() { + let filter = PropertyFilter::property("p1").is_in(vec![2u64.into()]); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_in(vec![2i64.into()]); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").is_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_in(vec![true.into()]); + let expected_results = vec!["N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_in(vec![6.0f64.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_in() { - search_nodes_for_property_in( - PersistentGraph::new, - vec!["N2", "N5", "N8", "N9"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1", "N2", "N7"], - vec!["N12", "N13", "N2", "N5", "N7", "N8"], - vec!["N1"], - ); - } + #[test] + fn test_nodes_filters_pg_for_property_in() { + let filter = PropertyFilter::property("p1").is_in(vec![2u64.into()]); + let expected_results = vec!["N2", "N5", "N8", "N9"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_in(vec![2i64.into()]); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").is_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_in(vec![true.into()]); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_in(vec![6.0f64.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - fn search_nodes_for_property_not_in( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").excludes(vec![1u64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").excludes(vec![2i64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").excludes(vec!["Paper_Airplane".into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").excludes(vec![true.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").excludes(vec![6.0f64.into()]); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + #[test] + fn test_nodes_filters_for_property_not_in() { + let filter = PropertyFilter::property("p1").is_not_in(vec![1u64.into()]); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_not_in(vec![2i64.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = + PropertyFilter::property("k2").is_not_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N2", "N5"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_not_in(vec![true.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_not_in(vec![6.0f64.into()]); + let expected_results = vec!["N2", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_for_property_not_in() { - search_nodes_for_property_not_in( - Graph::new, - vec!["N2", "N5"], - vec!["N1"], - vec!["N5"], - vec!["N1"], - vec!["N2", "N5", "N6"], - ); - } + #[test] + fn test_nodes_filters_pg_for_property_not_in() { + let filter = PropertyFilter::property("p1").is_not_in(vec![1u64.into()]); + let expected_results = vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_not_in(vec![2i64.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = + PropertyFilter::property("k2").is_not_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N12", "N13", "N2", "N5", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_not_in(vec![true.into()]); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_not_in(vec![6.0f64.into()]); + let expected_results = vec!["N12", "N13", "N2", "N5", "N6", "N7", "N8"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_not_in() { - search_nodes_for_property_not_in( - PersistentGraph::new, - vec!["N10", "N11", "N12", "N13", "N2", "N5", "N8", "N9"], - vec!["N1"], - vec!["N12", "N13", "N5", "N8"], - vec!["N1"], - vec!["N12", "N13", "N2", "N5", "N6", "N7", "N8"], - ); - } + #[test] + fn test_nodes_filters_for_property_is_some() { + let filter = PropertyFilter::property("p1").is_some(); + let expected_results = vec!["N1", "N2", "N3", "N5", "N6"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); - fn search_nodes_for_property_is_some(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").is_some(); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + let expected_results = Vec::::new(); + assert_filter_nodes_results_w!(init_graph, filter, 1..2, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 1..2, expected_results); - #[test] - fn test_search_nodes_graph_for_property_is_some() { - search_nodes_for_property_is_some(Graph::new, vec!["N1", "N2", "N3", "N5", "N6"]); - } + assert_filter_nodes_results_w!(init_graph, filter, 10..12, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 10..12, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_for_property_is_some() { - search_nodes_for_property_is_some( - PersistentGraph::new, - vec![ + #[test] + fn test_nodes_filters_pg_for_property_is_some() { + let filter = PropertyFilter::property("p1").is_some(); + let expected_results = vec![ "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N5", "N6", "N7", "N8", "N9", - ], - ); - } - - fn search_nodes_for_props_added_at_different_times( - constructor: F, - expected: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("q1") - .eq(0u64) - .and(PropertyFilter::property("p1").eq(3u64)); - let results = search_nodes(init_graph(constructor()), 1..4, filter); - assert_eq!(results, expected); - } - - #[test] - fn test_search_nodes_graph_for_props_added_at_different_times() { - search_nodes_for_props_added_at_different_times( - Graph::new, - vec!["N10", "N11", "N12", "N13"], - ); - } - - #[test] - fn test_search_nodes_persistent_graph_for_props_added_at_different_times() { - search_nodes_for_props_added_at_different_times( - PersistentGraph::new, - vec!["N10", "N11", "N12", "N13"], - ); - } + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let expected_results = Vec::::new(); + assert_filter_nodes_results_pg_w!(init_graph, filter, 1..2, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 1..2, expected_results); + + let expected_results = vec![ + "N1", "N10", "N11", "N12", "N13", "N14", "N15", "N2", "N3", "N4", "N5", "N6", + "N7", "N8", "N9", + ]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 10..12, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 10..12, expected_results); + } - fn fuzzy_search(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("k2").fuzzy_search("Paper_", 2, false); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_nodes_filters_for_props_added_at_different_times() { + let filter = PropertyFilter::property("q1") + .eq(0u64) + .and(PropertyFilter::property("p1").eq(3u64)); + let expected_results = vec!["N10", "N11", "N12", "N13"]; + assert_filter_nodes_results_w!(init_graph, filter, 1..4, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 1..4, expected_results); + } - #[test] - fn test_search_nodes_graph_fuzzy_search() { - fuzzy_search(Graph::new, vec!["N1", "N2"]); - } + #[test] + fn test_nodes_filters_pg_for_props_added_at_different_times() { + let filter = PropertyFilter::property("q1") + .eq(0u64) + .and(PropertyFilter::property("p1").eq(3u64)); + let expected_results = vec!["N10", "N11", "N12", "N13"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_fuzzy_search() { - fuzzy_search(PersistentGraph::new, vec!["N1", "N2", "N7"]); - } + #[test] + fn test_nodes_filters_fuzzy_search() { + let filter = PropertyFilter::property("k2").fuzzy_search("Paper_Airpla", 2, false); + let expected_results = vec!["N1"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - fn fuzzy_search_prefix_match(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - - let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, false); - let results = search_nodes(init_graph(constructor()), 6..9, filter); - assert_eq!(results, Vec::::new()); - } + #[test] + fn test_nodes_filters_pg_fuzzy_search() { + let filter = PropertyFilter::property("k2").fuzzy_search("Paper_Air", 5, false); + let expected_results = vec!["N1", "N2", "N7"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_graph_fuzzy_search_prefix_match() { - fuzzy_search_prefix_match(Graph::new, vec!["N1", "N2", "N5"]); - } + #[test] + fn test_nodes_filters_fuzzy_search_prefix_match() { + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); + let expected_results = vec!["N1", "N2"]; + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, false); + let expected_results = Vec::::new(); + assert_filter_nodes_results_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_w!(init_graph, filter, 6..9, expected_results); + } - #[test] - fn test_search_nodes_persistent_graph_fuzzy_search_prefix_match() { - fuzzy_search_prefix_match( - PersistentGraph::new, - vec!["N1", "N12", "N13", "N2", "N5", "N7", "N8"], - ); + #[test] + fn test_nodes_filters_pg_fuzzy_search_prefix_match() { + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); + let expected_results = vec!["N1", "N2", "N7"]; + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, false); + let expected_results = Vec::::new(); + assert_filter_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + assert_search_nodes_results_pg_w!(init_graph, filter, 6..9, expected_results); + } } - } - #[cfg(all(test, feature = "search"))] - mod search_edges_window_graph_tests { - use crate::{ - core::Prop, - db::{ - api::{ - mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, - view::{SearchableGraphOps, StaticGraphViewOps}, - }, - graph::views::{ - deletion_graph::PersistentGraph, - property_filter::{EdgeFilter, EdgeFilterOps, FilterExpr, PropertyFilterOps}, + mod test_edges_filters_window_graph { + use crate::{ + core::Prop, + db::{ + api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::StaticGraphViewOps, + }, + graph::views::{ + deletion_graph::PersistentGraph, + filter::{ + internal::InternalEdgeFilterOps, + model::{ + ComposableFilter, EdgeFilter, EdgeFilterOps, PropertyFilterOps, + }, + }, + test_helpers::filter_edges_with, + }, }, - }, - prelude::{ - AdditionOps, EdgeViewOps, Graph, NodeViewOps, PropertyAdditionOps, PropertyFilter, - TimeOps, - }, - }; - use raphtory_api::core::storage::arc_str::ArcStr; - use std::ops::Range; - - fn init_graph< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - let edges = vec![ - ( - 6, - "N1", - "N2", - vec![ - ("p1", Prop::U64(2u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - Some("air_nomad"), - ), - ( - 7, - "N1", - "N2", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(5i64)), - ("k3", Prop::Bool(false)), - ], - Some("air_nomad"), - ), - ( - 6, - "N2", - "N3", - vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(6.0f64))], - Some("water_tribe"), - ), - ( - 7, - "N2", - "N3", - vec![ - ("p1", Prop::U64(2u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - Some("water_tribe"), - ), - ( - 8, - "N3", - "N4", - vec![("p1", Prop::U64(1u64))], - Some("air_nomad"), - ), - ( - 9, - "N4", - "N5", - vec![("p1", Prop::U64(1u64))], - Some("air_nomad"), - ), - ( - 5, - "N5", - "N6", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - Some("air_nomad"), - ), - ( - 6, - "N5", - "N6", - vec![ - ("p1", Prop::U64(2u64)), - ("k2", Prop::Str(ArcStr::from("Pometry"))), - ("k4", Prop::F64(1.0f64)), - ], - Some("air_nomad"), - ), - ( - 5, - "N6", - "N7", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - ( - 6, - "N6", - "N7", - vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(1.0f64))], - Some("fire_nation"), - ), - ( - 3, - "N7", - "N8", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - Some("air_nomad"), - ), - ( - 5, - "N7", - "N8", - vec![("p1", Prop::U64(1u64))], - Some("air_nomad"), - ), - ( - 3, - "N8", - "N9", - vec![("p1", Prop::U64(1u64))], - Some("fire_nation"), - ), - ( - 4, - "N8", - "N9", - vec![ - ("p1", Prop::U64(2u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - Some("fire_nation"), - ), - (2, "N9", "N10", vec![("p1", Prop::U64(2u64))], None), - (2, "N10", "N11", vec![("q1", Prop::U64(0u64))], None), - (2, "N10", "N11", vec![("p1", Prop::U64(3u64))], None), - (2, "N11", "N12", vec![("p1", Prop::U64(3u64))], None), - (2, "N11", "N12", vec![("q1", Prop::U64(0u64))], None), - (2, "N12", "N13", vec![("q1", Prop::U64(0u64))], None), - ( - 3, - "N12", - "N13", - vec![ - ("p1", Prop::U64(3u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - None, - ), - (2, "N13", "N14", vec![("q1", Prop::U64(0u64))], None), - (3, "N13", "N14", vec![("p1", Prop::U64(3u64))], None), - (2, "N14", "N15", vec![("q1", Prop::U64(0u64))], None), - (2, "N15", "N1", vec![], None), - ]; - - for (id, src, dst, props, layer) in &edges { - graph - .add_edge(*id, src, dst, props.clone(), *layer) - .unwrap(); - } + prelude::{AdditionOps, Graph, PropertyAdditionOps, PropertyFilter, TimeOps}, + }; + use raphtory_api::core::storage::arc_str::ArcStr; + use std::{ops::Range, sync::Arc}; + + use crate::{assert_filter_results_w, assert_search_results_w}; + + fn init_graph< + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + >( + graph: G, + ) -> G { + let edges = vec![ + ( + 6, + "N1", + "N2", + vec![ + ("p1", Prop::U64(2u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + Some("air_nomad"), + ), + ( + 7, + "N1", + "N2", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(5i64)), + ("k3", Prop::Bool(false)), + ], + Some("air_nomad"), + ), + ( + 6, + "N2", + "N3", + vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(6.0f64))], + Some("water_tribe"), + ), + ( + 7, + "N2", + "N3", + vec![ + ("p1", Prop::U64(2u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + Some("water_tribe"), + ), + ( + 8, + "N3", + "N4", + vec![("p1", Prop::U64(1u64))], + Some("air_nomad"), + ), + ( + 9, + "N4", + "N5", + vec![("p1", Prop::U64(1u64))], + Some("air_nomad"), + ), + ( + 5, + "N5", + "N6", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + Some("air_nomad"), + ), + ( + 6, + "N5", + "N6", + vec![ + ("p1", Prop::U64(2u64)), + ("k2", Prop::Str(ArcStr::from("Pometry"))), + ("k4", Prop::F64(1.0f64)), + ], + Some("air_nomad"), + ), + ( + 5, + "N6", + "N7", + vec![("p1", Prop::U64(1u64))], + Some("fire_nation"), + ), + ( + 6, + "N6", + "N7", + vec![("p1", Prop::U64(1u64)), ("k4", Prop::F64(1.0f64))], + Some("fire_nation"), + ), + ( + 3, + "N7", + "N8", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Ship"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + Some("air_nomad"), + ), + ( + 5, + "N7", + "N8", + vec![("p1", Prop::U64(1u64))], + Some("air_nomad"), + ), + ( + 3, + "N8", + "N9", + vec![("p1", Prop::U64(1u64))], + Some("fire_nation"), + ), + ( + 4, + "N8", + "N9", + vec![ + ("p1", Prop::U64(2u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + Some("fire_nation"), + ), + (2, "N9", "N10", vec![("p1", Prop::U64(2u64))], None), + (2, "N10", "N11", vec![("q1", Prop::U64(0u64))], None), + (2, "N10", "N11", vec![("p1", Prop::U64(3u64))], None), + (2, "N11", "N12", vec![("p1", Prop::U64(3u64))], None), + (2, "N11", "N12", vec![("q1", Prop::U64(0u64))], None), + (2, "N12", "N13", vec![("q1", Prop::U64(0u64))], None), + ( + 3, + "N12", + "N13", + vec![ + ("p1", Prop::U64(3u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + None, + ), + (2, "N13", "N14", vec![("q1", Prop::U64(0u64))], None), + (3, "N13", "N14", vec![("p1", Prop::U64(3u64))], None), + ( + 2, + "N14", + "N15", + vec![ + ("q1", Prop::U64(0u64)), + ( + "x", + Prop::List(Arc::from(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ])), + ), + ], + None, + ), + (2, "N15", "N1", vec![], None), + ]; + + for (id, src, dst, props, layer) in &edges { + graph + .add_edge(*id, src, dst, props.clone(), *layer) + .unwrap(); + } + + // Constant property assignments + let constant_properties = vec![ + ( + "N1", + "N2", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(3i64)), + ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(6.0f64)), + ], + Some("air_nomad"), + ), + ("N4", "N5", vec![("p1", Prop::U64(2u64))], Some("air_nomad")), + ("N9", "N10", vec![("p1", Prop::U64(1u64))], None), + ("N10", "N11", vec![("p1", Prop::U64(1u64))], None), + ("N11", "N12", vec![("p1", Prop::U64(1u64))], None), + ("N12", "N13", vec![("p1", Prop::U64(1u64))], None), + ( + "N13", + "N14", + vec![ + ("p1", Prop::U64(1u64)), + ("k1", Prop::I64(2i64)), + ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), + ("k3", Prop::Bool(true)), + ("k4", Prop::F64(10.0f64)), + ], + None, + ), + ("N14", "N15", vec![("p1", Prop::U64(1u64))], None), + ("N15", "N1", vec![("p1", Prop::U64(1u64))], None), + ]; + + for (src, dst, props, layer) in constant_properties { + graph + .edge(src, dst) + .unwrap() + .add_constant_properties(props, layer) + .unwrap(); + } - // Constant property assignments - let constant_properties = vec![ - ( - "N1", - "N2", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(3i64)), - ("k2", Prop::Str(ArcStr::from("Paper_Airplane"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(6.0f64)), - ], - Some("air_nomad"), - ), - ("N4", "N5", vec![("p1", Prop::U64(2u64))], Some("air_nomad")), - ("N9", "N10", vec![("p1", Prop::U64(1u64))], None), - ("N10", "N11", vec![("p1", Prop::U64(1u64))], None), - ("N11", "N12", vec![("p1", Prop::U64(1u64))], None), - ("N12", "N13", vec![("p1", Prop::U64(1u64))], None), - ( - "N13", - "N14", - vec![ - ("p1", Prop::U64(1u64)), - ("k1", Prop::I64(2i64)), - ("k2", Prop::Str(ArcStr::from("Sand_Clown"))), - ("k3", Prop::Bool(true)), - ("k4", Prop::F64(10.0f64)), - ], - None, - ), - ("N14", "N15", vec![("p1", Prop::U64(1u64))], None), - ("N15", "N1", vec![("p1", Prop::U64(1u64))], None), - ]; - - for (src, dst, props, layer) in constant_properties { graph - .edge(src, dst) - .unwrap() - .add_constant_properties(props, layer) - .unwrap(); } - graph - } + fn filter_edges_w(filter: I, w: Range) -> Vec { + let graph = init_graph(Graph::new()); + filter_edges_with(filter, graph.window(w.start, w.end)) + } - fn search_edges< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - w: Range, - filter: FilterExpr, - ) -> Vec { - graph.create_index().unwrap(); - let mut results = graph - .window(w.start, w.end) - .search_edges(filter, 20, 0) - .expect("Failed to search for edges") - .into_iter() - .map(|v| format!("{}->{}", v.src().name(), v.dst().name())) - .collect::>(); - results.sort(); - results - } + #[allow(dead_code)] + fn filter_edges_pg_w( + filter: I, + w: Range, + ) -> Vec { + let graph = init_graph(PersistentGraph::new()); + filter_edges_with(filter, graph.window(w.start, w.end)) + } - fn search_edges_for_src_eq(constructor: F) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = EdgeFilter::src().eq("N2"); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N2->N3"]); - } + #[cfg(feature = "search")] + mod search_edges { + use std::ops::Range; + use crate::db::graph::views::deletion_graph::PersistentGraph; + use crate::db::graph::views::filter::model::AsEdgeFilter; + use crate::db::graph::views::test_helpers::search_edges_with; + use crate::db::graph::views::window_graph::views_test::test_filters_window_graph::test_edges_filters_window_graph::init_graph; + use crate::prelude::{Graph, TimeOps}; + + pub fn search_edges_w(filter: I, w: Range) -> Vec { + let graph = init_graph(Graph::new()); + search_edges_with(filter, graph.window(w.start, w.end)) + } + + pub fn search_edges_pg_w(filter: I, w: Range) -> Vec { + let graph = init_graph(PersistentGraph::new()); + search_edges_with(filter, graph.window(w.start, w.end)) + } + } - #[test] - fn test_search_edges_graph_for_src_eq() { - search_edges_for_src_eq(Graph::new); - } + #[cfg(feature = "search")] + use search_edges::*; - #[test] - fn test_search_edges_persistent_graph_for_src_eq() { - search_edges_for_src_eq(PersistentGraph::new); - } + #[test] + fn test_edges_filters_for_src_eq() { + let filter = EdgeFilter::src().name().eq("N2"); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - fn search_edges_for_src_ne(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = EdgeFilter::src().ne("N2"); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_edges_filters_pg_for_src_eq() { + let filter = EdgeFilter::src().name().eq("N2"); + let expected_results = vec!["N2->N3"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_src_ne() { - search_edges_for_src_ne(Graph::new, vec!["N1->N2", "N3->N4", "N5->N6", "N6->N7"]); - } + #[test] + fn test_edges_filters_for_src_ne() { + let filter = EdgeFilter::src().name().ne("N2"); + let expected_results = vec!["N1->N2", "N3->N4", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_src_ne() { - search_edges_for_src_ne( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_src_ne() { + let filter = EdgeFilter::src().name().ne("N2"); + let expected_results = vec![ "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", "N15->N1", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N9", "N9->N10", - ], - ); - } - - fn search_edges_for_dst_in(constructor: F) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = EdgeFilter::dst().includes(vec!["N2".into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N1->N2"]); - - let filter = EdgeFilter::dst().includes(vec!["N2".into(), "N5".into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, vec!["N1->N2"]); - } - - #[test] - fn test_search_edges_graph_for_dst_in() { - search_edges_for_dst_in(Graph::new); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_dst_in() { - search_edges_for_dst_in(PersistentGraph::new); - } + #[test] + fn test_edges_filters_for_dst_in() { + let filter = EdgeFilter::dst().name().is_in(vec!["N2".into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = EdgeFilter::dst() + .name() + .is_in(vec!["N2".into(), "N5".into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - fn search_edges_for_dst_not_in(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = EdgeFilter::dst().excludes(vec!["N5".into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_edges_filters_pg_for_dst_in() { + let filter = EdgeFilter::dst().name().is_in(vec!["N2".into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = EdgeFilter::dst() + .name() + .is_in(vec!["N2".into(), "N5".into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_dst_not_in() { - search_edges_for_dst_not_in( - Graph::new, - vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_dst_not_in() { + let filter = EdgeFilter::dst().name().is_not_in(vec!["N5".into()]); + let expected_results = vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_dst_not_in() { - search_edges_for_dst_not_in( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_dst_not_in() { + let filter = EdgeFilter::dst().name().is_not_in(vec!["N5".into()]); + let expected_results = vec![ "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", "N15->N1", "N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N9", "N9->N10", - ], - ); - } - - fn search_edges_for_property_eq( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").eq(1u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").eq(2i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").eq(true); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").eq(6.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_eq() { - search_edges_for_property_eq( - Graph::new, - vec!["N1->N2", "N3->N4", "N6->N7"], - vec!["N2->N3"], - vec!["N1->N2"], - vec!["N2->N3"], - vec!["N1->N2"], - ); - } + #[test] + fn test_edges_filters_for_property_eq() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").eq(2i64); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").eq(true); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").eq(6.0f64); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").eq(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = vec!["N14->N15"]; + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_eq() { - search_edges_for_property_eq( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_eq() { + let filter = PropertyFilter::property("p1").eq(1u64); + let expected_results = vec![ "N1->N2", "N14->N15", "N15->N1", "N3->N4", "N6->N7", "N7->N8", - ], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").eq(2i64); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2"], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").eq("Paper_Airplane"); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").eq(true); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2"], - ); - } - - fn search_edges_for_property_ne( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").ne(1u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").ne(2i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").ne(true); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").ne(6.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").eq(6.0f64); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").eq(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(6), + // Prop::U64(9), + // ]))); + // let expected_results = vec!["N14->N15"]; + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_ne() { - search_edges_for_property_ne( - Graph::new, - vec!["N2->N3", "N5->N6"], - vec!["N1->N2"], - vec!["N5->N6"], - vec!["N1->N2"], - vec!["N2->N3", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_property_ne() { + let filter = PropertyFilter::property("p1").ne(1u64); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ne(2i64); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").ne(true); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ne(6.0f64); + let expected_results = vec!["N2->N3", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ne(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = Vec::::new(); + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_ne() { - search_edges_for_property_ne( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_ne() { + let filter = PropertyFilter::property("p1").ne(1u64); + let expected_results = vec![ "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N8->N9", "N9->N10", - ], - vec!["N1->N2"], - vec!["N12->N13", "N13->N14", "N5->N6", "N8->N9"], - vec!["N1->N2"], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ne(2i64); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").ne("Paper_Airplane"); + let expected_results = vec![ + "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").ne(true); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ne(6.0f64); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N6->N7", "N7->N8", "N8->N9", - ], - ); - } - - fn search_edges_for_property_lt( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").lt(3u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").lt(3i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").lt(10.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").ne(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(6), + // Prop::U64(9), + // ]))); + // let expected_results = Vec::::new(); + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_lt() { - search_edges_for_property_lt( - Graph::new, - vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"], - vec!["N2->N3"], - vec!["N1->N2", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_property_lt() { + let filter = PropertyFilter::property("p1").lt(3u64); + let expected_results = vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").lt(3i64); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").lt(10.0f64); + let expected_results = vec!["N1->N2", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").lt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(7), + Prop::U64(0), + ]))); + let expected_results = vec!["N14->N15"]; + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_lt() { - search_edges_for_property_lt( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_lt() { + let filter = PropertyFilter::property("p1").lt(3u64); + let expected_results = vec![ "N1->N2", "N14->N15", "N15->N1", "N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N9", "N9->N10", - ], - vec![ - "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2", "N5->N6", "N6->N7"], - ); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); - fn search_edges_for_property_le( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").le(1u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").le(2i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").le(6.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + let filter = PropertyFilter::property("k1").lt(3i64); + let expected_results = vec![ + "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").lt(10.0f64); + let expected_results = vec!["N1->N2", "N5->N6", "N6->N7"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").lt(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(7), + // Prop::U64(0), + // ]))); + // let expected_results = vec!["N14->N15"]; + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_le() { - search_edges_for_property_le( - Graph::new, - vec!["N1->N2", "N3->N4", "N6->N7"], - vec!["N2->N3"], - vec!["N1->N2", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_property_le() { + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec!["N1->N2", "N3->N4", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").le(2i64); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").le(6.0f64); + let expected_results = vec!["N1->N2", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").le(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(7), + Prop::U64(0), + ]))); + let expected_results = vec!["N14->N15"]; + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_le() { - search_edges_for_property_le( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_le() { + let filter = PropertyFilter::property("p1").le(1u64); + let expected_results = vec![ "N1->N2", "N14->N15", "N15->N1", "N3->N4", "N6->N7", "N7->N8", - ], - vec![ - "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2", "N5->N6", "N6->N7"], - ); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); - fn search_edges_for_property_gt( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").gt(1u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").gt(2i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").gt(6.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + let filter = PropertyFilter::property("k1").le(2i64); + let expected_results = vec![ + "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").le(6.0f64); + let expected_results = vec!["N1->N2", "N5->N6", "N6->N7"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").le(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(2), + // Prop::U64(3), + // ]))); + // let expected_results = Vec::::new(); + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_gt() { - search_edges_for_property_gt( - Graph::new, - vec!["N2->N3", "N5->N6"], - vec!["N1->N2"], - vec!["N2->N3"], - ); - } + #[test] + fn test_edges_filters_for_property_gt() { + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").gt(2i64); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").gt(6.0f64); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").gt(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = Vec::::new(); + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_gt() { - search_edges_for_property_gt( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_gt() { + let filter = PropertyFilter::property("p1").gt(1u64); + let expected_results = vec![ "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N8->N9", "N9->N10", - ], - vec!["N1->N2"], - vec!["N12->N13", "N13->N14", "N2->N3", "N7->N8", "N8->N9"], - ); - } - - fn search_edges_for_property_ge( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").ge(1u64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").ge(2i64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k4").ge(6.0f64); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").gt(2i64); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").gt(6.0f64); + let expected_results = vec!["N12->N13", "N13->N14", "N2->N3", "N7->N8", "N8->N9"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").gt(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(2), + // Prop::U64(3), + // ]))); + // let expected_results = vec!["N14->N15"]; + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_ge() { - search_edges_for_property_ge( - Graph::new, - vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"], - vec!["N1->N2", "N2->N3"], - vec!["N1->N2", "N2->N3"], - ); - } + #[test] + fn test_edges_filters_for_property_ge() { + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ge(2i64); + let expected_results = vec!["N1->N2", "N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ge(6.0f64); + let expected_results = vec!["N1->N2", "N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("x").ge(Prop::List(Arc::new(vec![ + Prop::U64(1), + Prop::U64(6), + Prop::U64(9), + ]))); + let expected_results = vec!["N14->N15"]; + assert_filter_results_w!(filter_edges_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_w, filter, 1..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_ge() { - search_edges_for_property_ge( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_ge() { + let filter = PropertyFilter::property("p1").ge(1u64); + let expected_results = vec![ "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", "N15->N1", "N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N9", "N9->N10", - ], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").ge(2i64); + let expected_results = vec![ "N1->N2", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").ge(6.0f64); + let expected_results = vec![ "N1->N2", "N12->N13", "N13->N14", "N2->N3", "N7->N8", "N8->N9", - ], - ); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + // TODO: PropertyFilteringNotImplemented + // let filter = PropertyFilter::property("x").ge(Prop::List(Arc::new(vec![ + // Prop::U64(1), + // Prop::U64(2), + // Prop::U64(3), + // ]))); + // let expected_results = vec!["N14->N15"]; + // assert_filter_results_w!(filter_edges_pg_w, filter, 1..9, expected_results); + // TODO: Search APIs don't support list yet + // assert_search_results_w!(search_edges_pg_w, filter, 1..9, expected_results); + } - fn search_edges_for_property_in( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").includes(vec![2u64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").includes(vec![2i64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").includes(vec!["Paper_Airplane".into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").includes(vec![true.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").includes(vec![6.0f64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + #[test] + fn test_edges_filters_for_property_in() { + let filter = PropertyFilter::property("p1").is_in(vec![2u64.into()]); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_in(vec![2i64.into()]); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").is_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_in(vec![true.into()]); + let expected_results = vec!["N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_in(vec![6.0f64.into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_in() { - search_edges_for_property_in( - Graph::new, - vec!["N2->N3", "N5->N6"], - vec!["N2->N3"], - vec!["N1->N2", "N2->N3"], - vec!["N2->N3"], - vec!["N1->N2"], - ); - } + #[test] + fn test_edges_filters_pg_for_property_in() { + let filter = PropertyFilter::property("p1").is_in(vec![2u64.into()]); + let expected_results = vec!["N2->N3", "N5->N6", "N8->N9", "N9->N10"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); - #[test] - fn test_search_edges_persistent_graph_for_property_in() { - search_edges_for_property_in( - PersistentGraph::new, - vec!["N2->N3", "N5->N6", "N8->N9", "N9->N10"], - vec![ + let filter = PropertyFilter::property("k1").is_in(vec![2i64.into()]); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2", "N2->N3", "N7->N8"], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").is_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_in(vec![true.into()]); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - vec!["N1->N2"], - ); - } - - fn search_edges_for_property_not_in( - constructor: F, - expected1: Vec<&str>, - expected2: Vec<&str>, - expected3: Vec<&str>, - expected4: Vec<&str>, - expected5: Vec<&str>, - ) where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").excludes(vec![1u64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected1); - - let filter = PropertyFilter::property("k1").excludes(vec![2i64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected2); - - let filter = PropertyFilter::property("k2").excludes(vec!["Paper_Airplane".into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected3); - - let filter = PropertyFilter::property("k3").excludes(vec![true.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected4); - - let filter = PropertyFilter::property("k4").excludes(vec![6.0f64.into()]); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected5); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_in(vec![6.0f64.into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_not_in() { - search_edges_for_property_not_in( - Graph::new, - vec!["N2->N3", "N5->N6"], - vec!["N1->N2"], - vec!["N5->N6"], - vec!["N1->N2"], - vec!["N2->N3", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_property_not_in() { + let filter = PropertyFilter::property("p1").is_not_in(vec![1u64.into()]); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_not_in(vec![2i64.into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = + PropertyFilter::property("k2").is_not_in(vec!["Paper_Airplane".into()]); + let expected_results = vec!["N2->N3", "N5->N6"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_not_in(vec![true.into()]); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_not_in(vec![6.0f64.into()]); + let expected_results = vec!["N2->N3", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_not_in() { - search_edges_for_property_not_in( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_not_in() { + let filter = PropertyFilter::property("p1").is_not_in(vec![1u64.into()]); + let expected_results = vec![ "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N8->N9", "N9->N10", - ], - vec!["N1->N2"], - vec!["N12->N13", "N13->N14", "N5->N6", "N8->N9"], - vec!["N1->N2"], - vec![ + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k1").is_not_in(vec![2i64.into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = + PropertyFilter::property("k2").is_not_in(vec!["Paper_Airplane".into()]); + let expected_results = vec![ + "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k3").is_not_in(vec![true.into()]); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k4").is_not_in(vec![6.0f64.into()]); + let expected_results = vec![ "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N6->N7", "N7->N8", "N8->N9", - ], - ); - } - - fn search_edges_for_property_is_some(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("p1").is_some(); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_for_property_is_some() { - search_edges_for_property_is_some( - Graph::new, - vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"], - ); - } + #[test] + fn test_edges_filters_for_property_is_some() { + let filter = PropertyFilter::property("p1").is_some(); + let expected_results = vec!["N1->N2", "N2->N3", "N3->N4", "N5->N6", "N6->N7"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_for_property_is_some() { - search_edges_for_property_is_some( - PersistentGraph::new, - vec![ + #[test] + fn test_edges_filters_pg_for_property_is_some() { + let filter = PropertyFilter::property("p1").is_some(); + let expected_results = vec![ "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", "N15->N1", "N2->N3", "N3->N4", "N5->N6", "N6->N7", "N7->N8", "N8->N9", "N9->N10", - ], - ); - } - - fn search_edge_by_src_dst(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = EdgeFilter::src().eq("N1").and(EdgeFilter::dst().eq("N2")); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } - - #[test] - fn test_search_edges_graph_by_src_dst() { - search_edge_by_src_dst(Graph::new, vec!["N1->N2"]); - } - - #[test] - fn test_search_edges_persistent_graph_by_src_dst() { - search_edge_by_src_dst(PersistentGraph::new, vec!["N1->N2"]); - } + ]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - fn fuzzy_search(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("k2").fuzzy_search("Paper_", 2, false); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - } + #[test] + fn test_edges_filters_for_src_dst() { + let filter = EdgeFilter::src() + .name() + .eq("N1") + .and(EdgeFilter::dst().name().eq("N2")); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_fuzzy_search() { - fuzzy_search(Graph::new, vec!["N1->N2", "N2->N3"]); - } + #[test] + fn test_edges_filters_pg_for_src_dst() { + let filter = EdgeFilter::src() + .name() + .eq("N1") + .and(EdgeFilter::dst().name().eq("N2")); + let expected_results = vec!["N1->N2"]; + // TODO: PropertyFilteringNotImplemented + // assert_filter_results_w!(filter_edges_pg_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_fuzzy_search() { - fuzzy_search(PersistentGraph::new, vec!["N1->N2", "N2->N3", "N7->N8"]); - } + #[test] + fn test_edges_filters_fuzzy_search() { + let filter = PropertyFilter::property("k2").fuzzy_search("Paper_Airpla", 2, false); + let expected_results = vec!["N1->N2"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - fn fuzzy_search_prefix_match(constructor: F, expected: Vec<&str>) - where - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - F: Fn() -> G, - { - let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, expected); - - let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, false); - let results = search_edges(init_graph(constructor()), 6..9, filter); - assert_eq!(results, Vec::::new()); - } + #[test] + #[ignore] + // TODO: PropertyFilteringNotImplemented + fn test_edges_filters_pg_fuzzy_search() { + let filter = PropertyFilter::property("k2").fuzzy_search("Paper_", 2, false); + let expected_results = vec!["N1->N2", "N2->N3", "N7->N8"]; + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_graph_fuzzy_search_prefix_match() { - fuzzy_search_prefix_match(Graph::new, vec!["N1->N2", "N2->N3", "N5->N6"]); - } + #[test] + fn test_edges_filters_fuzzy_search_prefix_match() { + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); + let expected_results = vec!["N1->N2", "N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); + let expected_results = vec!["N1->N2", "N2->N3"]; + assert_filter_results_w!(filter_edges_w, filter, 6..9, expected_results); + assert_search_results_w!(search_edges_w, filter, 6..9, expected_results); + } - #[test] - fn test_search_edges_persistent_graph_fuzzy_search_prefix_match() { - fuzzy_search_prefix_match( - PersistentGraph::new, - vec![ + #[test] + #[ignore] + // TODO: PropertyFilteringNotImplemented + fn test_edges_filters_pg_fuzzy_search_prefix_match() { + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, true); + let expected_results = vec![ "N1->N2", "N12->N13", "N13->N14", "N2->N3", "N5->N6", "N7->N8", "N8->N9", - ], - ); + ]; + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + + let filter = PropertyFilter::property("k2").fuzzy_search("Pa", 2, false); + let expected_results = Vec::::new(); + assert_search_results_w!(search_edges_pg_w, filter, 6..9, expected_results); + } } } } diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs index 0193eabc5d..9e67bfb3ab 100644 --- a/raphtory/src/db/task/node/eval_node.rs +++ b/raphtory/src/db/task/node/eval_node.rs @@ -21,7 +21,10 @@ use crate::{ }, prelude::GraphViewOps, }; -use std::{cell::Ref, sync::Arc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + sync::Arc, +}; pub struct EvalNodeView<'graph, 'a: 'graph, G, S, GH = &'graph G, CS: Clone = ComputeStateVec> { pub node: VID, @@ -117,14 +120,20 @@ impl< i } + fn node_state(&self) -> Ref<'_, EVState<'a, CS>> { + RefCell::borrow(&self.eval_graph.node_state) + } + + fn node_state_mut(&self) -> RefMut<'_, EVState<'a, CS>> { + RefCell::borrow_mut(&self.eval_graph.node_state) + } + pub fn update>( &self, id: &AccId, a: IN, ) { - self.eval_graph - .node_state - .borrow_mut() + self.node_state_mut() .shard_mut() .accumulate_into(self.eval_graph.ss, self.pid(), a, id); } @@ -134,9 +143,7 @@ impl< id: &AccId, a: IN, ) { - self.eval_graph - .node_state - .borrow_mut() + self.node_state_mut() .global_mut() .accumulate_global(self.eval_graph.ss, a, id); } @@ -166,9 +173,7 @@ impl< OUT: StateType, A: StateType, { - self.eval_graph - .node_state - .borrow() + self.node_state() .global() .read_global(self.eval_graph.ss, agg) } @@ -183,9 +188,7 @@ impl< A: StateType, OUT: std::fmt::Debug, { - self.eval_graph - .node_state - .borrow() + self.node_state() .shard() .read_with_pid(self.eval_graph.ss, self.pid(), agg_r) .unwrap_or(ACC::finish(&ACC::zero())) @@ -201,12 +204,7 @@ impl< A: StateType, OUT: std::fmt::Debug, { - Entry::new( - self.eval_graph.node_state.borrow(), - *agg_r, - &self.node, - self.eval_graph.ss, - ) + Entry::new(self.node_state(), *agg_r, &self.node, self.eval_graph.ss) } /// Read the prev value of the node state using the given accumulator. @@ -219,9 +217,7 @@ impl< A: StateType, OUT: std::fmt::Debug, { - self.eval_graph - .node_state - .borrow() + self.node_state() .shard() .read_with_pid(self.eval_graph.ss + 1, self.pid(), agg_r) .unwrap_or(ACC::finish(&ACC::zero())) @@ -235,9 +231,7 @@ impl< A: StateType, OUT: std::fmt::Debug, { - self.eval_graph - .node_state - .borrow() + self.node_state() .global() .read_global(self.eval_graph.ss + 1, agg_r) .unwrap_or(ACC::finish(&ACC::zero())) @@ -277,7 +271,7 @@ impl< .map(move |v| EvalNodeView::new_filtered(v, base_graph.clone(), graph.clone(), None)) } - pub fn type_filter(&self, node_types: &[impl AsRef]) -> Self { + pub fn type_filter, V: AsRef>(&self, node_types: I) -> Self { let node_types_filter = create_node_type_filter(self.graph.node_meta().node_type_meta(), node_types); diff --git a/raphtory/src/lib.rs b/raphtory/src/lib.rs index cd178988b8..d91c9aedaa 100644 --- a/raphtory/src/lib.rs +++ b/raphtory/src/lib.rs @@ -121,12 +121,18 @@ pub mod prelude { AsOrderedNodeStateOps, NodeStateGroupBy, NodeStateOps, OrderedNodeStateOps, }, view::{ - EdgePropertyFilterOps, EdgeViewOps, ExplodedEdgePropertyFilterOps, - GraphViewOps, Layer, LayerOps, NodePropertyFilterOps, NodeViewOps, ResetFilter, + EdgePropertyFilterOps, + EdgeViewOps, // ExplodedEdgePropertyFilterOps, + GraphViewOps, + Layer, + LayerOps, + NodePropertyFilterOps, + NodeViewOps, + ResetFilter, TimeOps, }, }, - graph::{graph::Graph, views::property_filter::PropertyFilter}, + graph::{graph::Graph, views::filter::model::property_filter::PropertyFilter}, }, }; pub use raphtory_api::core::{entities::GID, input::input_node::InputNode}; diff --git a/raphtory/src/python/graph/graph.rs b/raphtory/src/python/graph/graph.rs index bbe01b6e8e..d53a5a1305 100644 --- a/raphtory/src/python/graph/graph.rs +++ b/raphtory/src/python/graph/graph.rs @@ -22,7 +22,7 @@ use crate::{ }, serialise::{ parquet::{ParquetDecoder, ParquetEncoder}, - StableDecode, StableEncode, + InternalStableDecode, StableEncode, }, }; use pyo3::{prelude::*, pybacked::PyBackedStr, types::PyDict}; diff --git a/raphtory/src/python/graph/graph_with_deletions.rs b/raphtory/src/python/graph/graph_with_deletions.rs index 76a478e413..36744c9a29 100644 --- a/raphtory/src/python/graph/graph_with_deletions.rs +++ b/raphtory/src/python/graph/graph_with_deletions.rs @@ -18,8 +18,9 @@ use crate::{ }, graph::{edge::EdgeView, node::NodeView, views::deletion_graph::PersistentGraph}, }, + disk_graph::DiskGraphStorage, io::parquet_loaders::*, - prelude::{DeletionOps, GraphViewOps, ImportOps}, + prelude::{DeletionOps, Graph, GraphViewOps, ImportOps}, python::{ graph::{edge::PyEdge, node::PyNode, views::graph_view::PyGraphView}, utils::{PyNodeRef, PyTime}, @@ -106,6 +107,17 @@ impl PyPersistentGraph { ) } + #[cfg(feature = "storage")] + pub fn to_disk_graph(&self, graph_dir: PathBuf) -> Result { + use crate::db::api::storage::graph::storage_ops::GraphStorage; + use std::sync::Arc; + + let disk_graph = DiskGraphStorage::from_graph(&self.graph.event_graph(), graph_dir)?; + let storage = GraphStorage::Disk(Arc::new(disk_graph)); + let graph = PersistentGraph::from_internal_graph(storage); + Ok(graph) + } + fn __reduce__(&self) -> (PyGraphEncoder, (Vec,)) { let state = self.graph.encode_to_vec(); (PyGraphEncoder, (state,)) diff --git a/raphtory/src/python/graph/index.rs b/raphtory/src/python/graph/index.rs index 20b5e4d3e9..68b957816d 100644 --- a/raphtory/src/python/graph/index.rs +++ b/raphtory/src/python/graph/index.rs @@ -16,6 +16,14 @@ impl PyGraphView { self.graph.create_index() } + /// Creates a graph index in memory (RAM). + /// + /// This is primarily intended for use in tests and should not be used in production environments, + /// as the index will not be persisted to disk. + fn create_index_in_ram(&self) -> Result<(), GraphError> { + self.graph.create_index_in_ram() + } + /// Searches for nodes which match the given filter expression. This uses Tantivy's exact search. /// /// Arguments: @@ -32,7 +40,8 @@ impl PyGraphView { limit: usize, offset: usize, ) -> Result>, GraphError> { - self.graph.search_nodes(filter.0.clone(), limit, offset) + let filter = filter.try_as_node_filter()?; + self.graph.search_nodes(filter, limit, offset) } /// Searches for edges which match the given filter expression. This uses Tantivy's exact search. @@ -51,6 +60,7 @@ impl PyGraphView { limit: usize, offset: usize, ) -> Result>, GraphError> { - self.graph.search_edges(filter.0.clone(), limit, offset) + let filter = filter.try_as_edge_filter()?; + self.graph.search_edges(filter, limit, offset) } } diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 27776c6fd7..73b3fe538a 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -13,7 +13,7 @@ use crate::{ state::{ops, LazyNodeState, NodeStateOps}, view::{ internal::{ - CoreGraphOps, DynOrMutableGraph, DynamicGraph, IntoDynamic, + CoreGraphOps, DynOrMutableGraph, DynamicGraph, IntoDynHop, IntoDynamic, IntoDynamicOrMutable, MaterializedGraph, }, *, @@ -23,7 +23,7 @@ use crate::{ node::NodeView, nodes::Nodes, path::{PathFromGraph, PathFromNode}, - views::property_filter::internal::*, + views::filter::internal::*, }, }, python::{ @@ -34,7 +34,7 @@ use crate::{ types::{ iterable::FromIterable, repr::StructReprBuilder, - wrappers::{iterables::*, prop::PyPropertyFilter}, + wrappers::{filter_expr::PyFilterExpr, iterables::*, prop::PyPropertyFilter}, }, utils::{PyNodeRef, PyTime}, }, diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index f9fbb1b83b..715d24dc17 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -6,7 +6,9 @@ use crate::{ api::{ properties::Properties, view::{ - internal::{DynamicGraph, IntoDynamic, MaterializedGraph, OneHopFilter}, + internal::{ + DynamicGraph, IntoDynHop, IntoDynamic, MaterializedGraph, OneHopFilter, + }, LayerOps, StaticGraphViewOps, }, }, @@ -18,14 +20,13 @@ use crate::{ nodes::Nodes, views::{ cached_view::CachedView, + filter::{ + edge_property_filtered_graph::EdgePropertyFilteredGraph, internal::*, + node_property_filtered_graph::NodePropertyFilteredGraph, + node_type_filtered_graph::NodeTypeFilteredGraph, + }, layer_graph::LayeredGraph, node_subgraph::NodeSubgraph, - node_type_filtered_subgraph::TypeFilteredSubgraph, - property_filter::{ - edge_property_filter::EdgePropertyFilteredGraph, - exploded_edge_property_filter::ExplodedEdgePropertyFilteredGraph, internal::*, - node_property_filter::NodePropertyFilteredGraph, - }, window_graph::WindowedGraph, }, }, @@ -35,7 +36,7 @@ use crate::{ graph::{edge::PyEdge, node::PyNode}, types::{ repr::{Repr, StructReprBuilder}, - wrappers::prop::PyPropertyFilter, + wrappers::{filter_expr::PyFilterExpr, prop::PyPropertyFilter}, }, utils::PyNodeRef, }, @@ -137,7 +138,7 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for CachedView< } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for TypeFilteredSubgraph { +impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodeTypeFilteredGraph { type Target = PyGraphView; type Output = >::Output; type Error = >::Error; @@ -167,17 +168,17 @@ impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> for NodePropert } } -impl<'py, G: StaticGraphViewOps + IntoDynamic> IntoPyObject<'py> - for ExplodedEdgePropertyFilteredGraph -{ - 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 +// { +// type Target = PyGraphView; +// type Output = >::Output; +// type Error = >::Error; +// +// fn into_pyobject(self, py: Python<'py>) -> Result { +// PyGraphView::from(self).into_pyobject(py) +// } +// } /// The API for querying a view of the graph in a read-only state #[pymethods] @@ -413,7 +414,7 @@ impl PyGraphView { /// /// Returns: /// GraphView: Returns the subgraph - fn subgraph_node_types(&self, node_types: Vec) -> TypeFilteredSubgraph { + fn subgraph_node_types(&self, node_types: Vec) -> NodeTypeFilteredGraph { self.graph.subgraph_node_types(node_types) } diff --git a/raphtory/src/python/types/iterable.rs b/raphtory/src/python/types/iterable.rs index 9abf80ae95..cc9e923413 100644 --- a/raphtory/src/python/types/iterable.rs +++ b/raphtory/src/python/types/iterable.rs @@ -7,6 +7,7 @@ use std::{ marker::PhantomData, ops::{Deref, DerefMut}, sync::Arc, + vec::IntoIter, }; pub struct Iterable IntoPyObject<'py> + From + Repr> { @@ -131,12 +132,27 @@ impl Deref for FromIterable { } } +impl From> for Vec { + fn from(v: FromIterable) -> Self { + v.0 + } +} + impl DerefMut for FromIterable { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } +impl IntoIterator for FromIterable { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + impl<'py, T: FromPyObject<'py>> FromPyObject<'py> for FromIterable { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let len = ob.len().unwrap_or(0); diff --git a/raphtory/src/python/types/macros/trait_impl/edge_property_filter_ops.rs b/raphtory/src/python/types/macros/trait_impl/edge_property_filter_ops.rs index 37dc491cf3..96097613f2 100644 --- a/raphtory/src/python/types/macros/trait_impl/edge_property_filter_ops.rs +++ b/raphtory/src/python/types/macros/trait_impl/edge_property_filter_ops.rs @@ -19,41 +19,34 @@ macro_rules! impl_edge_property_filter_ops { #[doc=concat!(" ", $name, ": The filtered view")] fn filter_edges( &self, - filter: $crate::python::types::wrappers::prop::PyPropertyFilter, - ) -> Result< - <$base_type as OneHopFilter<'static>>::Filtered< - ::EdgeFiltered< - 'static, - <$base_type as OneHopFilter<'static>>::FilteredGraph, - >, - >, - GraphError, - > { - self.$field.clone().filter_edges(filter) + filter: PyFilterExpr, + ) -> Result<<$base_type as OneHopFilter<'static>>::Filtered, GraphError> + { + Ok(self.$field.clone().filter_edges(filter)?.into_dyn_hop()) } - /// Return a filtered view that only includes exploded edges that satisfy the filter - /// - /// Arguments: - /// filter (PropertyFilter): The filter to apply to the exploded edge properties. Construct a - /// filter using `Prop`. - /// - /// Returns: - #[doc=concat!(" ", $name, ": The filtered view")] - fn filter_exploded_edges( - &self, - filter: $crate::python::types::wrappers::prop::PyPropertyFilter, - ) -> Result< - <$base_type as OneHopFilter<'static>>::Filtered< - ::ExplodedEdgeFiltered< - 'static, - <$base_type as OneHopFilter<'static>>::FilteredGraph, - >, - >, - GraphError, - > { - self.$field.filter_exploded_edges(filter) - } + // /// Return a filtered view that only includes exploded edges that satisfy the filter + // /// + // /// Arguments: + // /// filter (PropertyFilter): The filter to apply to the exploded edge properties. Construct a + // /// filter using `Prop`. + // /// + // /// Returns: + // #[doc=concat!(" ", $name, ": The filtered view")] + // fn filter_exploded_edges( + // &self, + // filter: $crate::python::types::wrappers::prop::PyPropertyFilter, + // ) -> Result< + // <$base_type as OneHopFilter<'static>>::Filtered< + // ::ExplodedEdgeFiltered< + // 'static, + // <$base_type as OneHopFilter<'static>>::FilteredGraph, + // >, + // >, + // GraphError, + // > { + // self.$field.filter_exploded_edges(filter) + // } } }; } diff --git a/raphtory/src/python/types/macros/trait_impl/node_property_filter_ops.rs b/raphtory/src/python/types/macros/trait_impl/node_property_filter_ops.rs index dbcd40219f..c1c97f26c5 100644 --- a/raphtory/src/python/types/macros/trait_impl/node_property_filter_ops.rs +++ b/raphtory/src/python/types/macros/trait_impl/node_property_filter_ops.rs @@ -19,17 +19,10 @@ macro_rules! impl_node_property_filter_ops { #[doc=concat!(" ", $name, ": The filtered view")] fn filter_nodes( &self, - filter: $crate::python::types::wrappers::prop::PyPropertyFilter, - ) -> Result< - <$base_type as OneHopFilter<'static>>::Filtered< - ::NodePropertyFiltered< - 'static, - <$base_type as OneHopFilter<'static>>::FilteredGraph, - >, - >, - GraphError, - > { - self.$field.clone().filter_nodes(filter) + filter: PyFilterExpr, + ) -> Result<<$base_type as OneHopFilter<'static>>::Filtered, GraphError> + { + Ok(self.$field.clone().filter_nodes(filter)?.into_dyn_hop()) } } }; diff --git a/raphtory/src/python/types/macros/trait_impl/serialise.rs b/raphtory/src/python/types/macros/trait_impl/serialise.rs index 97a5de74bc..96aee63bf0 100644 --- a/raphtory/src/python/types/macros/trait_impl/serialise.rs +++ b/raphtory/src/python/types/macros/trait_impl/serialise.rs @@ -89,7 +89,7 @@ macro_rules! impl_serialise { #[doc = concat!(" ", $name, ":")] #[staticmethod] fn deserialise(bytes: &[u8]) -> Result<$base_type, GraphError> { - <$base_type as $crate::serialise::StableDecode>::decode_from_bytes(bytes) + <$base_type as $crate::serialise::InternalStableDecode>::decode_from_bytes(bytes) } #[doc = concat!(" Serialise ", $name, " to bytes.")] diff --git a/raphtory/src/python/types/wrappers/filter_expr.rs b/raphtory/src/python/types/wrappers/filter_expr.rs index 166ef25dd7..a4f3342d1b 100644 --- a/raphtory/src/python/types/wrappers/filter_expr.rs +++ b/raphtory/src/python/types/wrappers/filter_expr.rs @@ -1,26 +1,179 @@ use crate::{ - core::Prop, - db::graph::views::property_filter::{ - EdgeFilter, EdgeFilterOps, FilterExpr, InternalEdgeFilterOps, InternalNodeFilterOps, - InternalPropertyFilterOps, NodeFilter, NodeFilterOps, PropertyFilterBuilder, - PropertyFilterOps, TemporalPropertyFilterBuilder, + core::{utils::errors::GraphError, Prop}, + db::graph::views::filter::model::{ + edge_filter::CompositeEdgeFilter, node_filter::CompositeNodeFilter, AndFilter, + AsEdgeFilter, AsNodeFilter, EdgeEndpointFilter, EdgeFilter, EdgeFilterOps, + InternalEdgeFilterBuilderOps, InternalNodeFilterBuilderOps, InternalPropertyFilterOps, + NodeFilter, NotFilter, OrFilter, PropertyFilterBuilder, PropertyFilterOps, + TemporalPropertyFilterBuilder, + }, + python::types::{ + iterable::FromIterable, + wrappers::prop::{ + DynInternalEdgeFilterOps, DynInternalNodeFilterOps, DynNodeFilterBuilderOps, + }, }, }; use pyo3::prelude::*; use std::sync::Arc; +pub trait AsPropertyFilter: DynInternalNodeFilterOps + DynInternalEdgeFilterOps {} + +impl AsPropertyFilter for T {} + +#[derive(Clone)] +pub enum PyInnerFilterExpr { + Node(Arc), + Edge(Arc), + Property(Arc), +} + #[pyclass(frozen, name = "FilterExpr", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyFilterExpr(pub FilterExpr); +pub struct PyFilterExpr(pub PyInnerFilterExpr); -#[pymethods] impl PyFilterExpr { - fn __and__(&self, other: &PyFilterExpr) -> PyFilterExpr { - PyFilterExpr(self.0.clone().and(other.0.clone())) + pub fn try_as_node_filter(&self) -> Result { + match &self.0 { + PyInnerFilterExpr::Node(i) => Ok(i.as_node_filter()), + PyInnerFilterExpr::Property(i) => Ok(i.as_node_filter()), + PyInnerFilterExpr::Edge(_) => Err(GraphError::ParsingError), + } } - fn __or__(&self, other: &PyFilterExpr) -> PyFilterExpr { - PyFilterExpr(self.0.clone().or(other.0.clone())) + pub fn try_as_edge_filter(&self) -> Result { + match &self.0 { + PyInnerFilterExpr::Edge(i) => Ok(i.as_edge_filter()), + PyInnerFilterExpr::Property(i) => Ok(i.as_edge_filter()), + PyInnerFilterExpr::Node(_) => Err(GraphError::ParsingError), + } + } +} + +#[pymethods] +impl PyFilterExpr { + pub fn __and__(&self, other: &Self) -> Result { + match &self.0 { + PyInnerFilterExpr::Node(i) => match &other.0 { + PyInnerFilterExpr::Node(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Property(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Edge(_) => Err(GraphError::ParsingError), + }, + PyInnerFilterExpr::Edge(i) => match &other.0 { + PyInnerFilterExpr::Edge(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Property(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Node(_) => Err(GraphError::ParsingError), + }, + PyInnerFilterExpr::Property(i) => match &other.0 { + PyInnerFilterExpr::Property(j) => Ok(PyFilterExpr(PyInnerFilterExpr::Property( + Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + }), + ))), + PyInnerFilterExpr::Node(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Edge(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(AndFilter { + left: i.clone(), + right: j.clone(), + })))) + } + }, + } + } + + pub fn __or__(&self, other: &Self) -> Result { + match &self.0 { + PyInnerFilterExpr::Node(i) => match &other.0 { + PyInnerFilterExpr::Node(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Property(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Edge(_) => Err(GraphError::ParsingError), + }, + PyInnerFilterExpr::Edge(i) => match &other.0 { + PyInnerFilterExpr::Edge(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Property(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Node(_) => Err(GraphError::ParsingError), + }, + PyInnerFilterExpr::Property(i) => match &other.0 { + PyInnerFilterExpr::Property(j) => Ok(PyFilterExpr(PyInnerFilterExpr::Property( + Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + }), + ))), + PyInnerFilterExpr::Node(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + PyInnerFilterExpr::Edge(j) => { + Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(OrFilter { + left: i.clone(), + right: j.clone(), + })))) + } + }, + } + } + + fn __invert__(&self) -> Result { + match &self.0 { + PyInnerFilterExpr::Node(i) => Ok(PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NotFilter(i.clone()), + )))), + PyInnerFilterExpr::Edge(i) => Ok(PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new( + NotFilter(i.clone()), + )))), + PyInnerFilterExpr::Property(i) => Ok(PyFilterExpr(PyInnerFilterExpr::Property( + Arc::new(NotFilter(i.clone())), + ))), + } } } @@ -41,43 +194,63 @@ impl From for PyPropertyFilterOps { #[pymethods] impl PyPropertyFilterOps { fn __eq__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.eq(value)) + let property = self.0.eq(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn __ne__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.ne(value)) + let property = self.0.ne(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn __lt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.lt(value)) + let property = self.0.lt(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn __le__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.le(value)) + let property = self.0.le(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn __gt__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.gt(value)) + let property = self.0.gt(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn __ge__(&self, value: Prop) -> PyFilterExpr { - PyFilterExpr(self.0.ge(value)) + let property = self.0.ge(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } - fn includes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.includes(values)) + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + let property = self.0.is_in(values); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } - fn excludes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.excludes(values)) + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + let property = self.0.is_not_in(values); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn is_none(&self) -> PyFilterExpr { - PyFilterExpr(self.0.is_none()) + let property = self.0.is_none(); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn is_some(&self) -> PyFilterExpr { - PyFilterExpr(self.0.is_some()) + let property = self.0.is_some(); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) + } + + fn contains(&self, value: Prop) -> PyFilterExpr { + let property = self.0.contains(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) + } + + fn not_contains(&self, value: Prop) -> PyFilterExpr { + let property = self.0.not_contains(value); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } fn fuzzy_search( @@ -86,10 +259,10 @@ impl PyPropertyFilterOps { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr( - self.0 - .fuzzy_search(prop_value, levenshtein_distance, prefix_match), - ) + let property = self + .0 + .fuzzy_search(prop_value, levenshtein_distance, prefix_match); + PyFilterExpr(PyInnerFilterExpr::Property(Arc::new(property))) } } @@ -112,7 +285,8 @@ impl PyTemporalPropertyFilterBuilder { } } -#[pyclass(frozen, name = "PropertyFilterBuilder", module = "raphtory.filter", extends=PyPropertyFilterOps)] +#[pyclass(frozen, name = "PropertyFilterBuilder", module = "raphtory.filter", extends=PyPropertyFilterOps +)] #[derive(Clone)] pub struct PyPropertyFilterBuilder(PropertyFilterBuilder); @@ -154,9 +328,9 @@ impl PyPropertyFilterBuilder { #[pyclass(frozen, name = "NodeFilterOp", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyNodeFilterOp(Arc); +pub struct PyNodeFilterOp(Arc); -impl From for PyNodeFilterOp { +impl From for PyNodeFilterOp { fn from(value: T) -> Self { PyNodeFilterOp(Arc::new(value)) } @@ -165,19 +339,27 @@ impl From for PyNodeFilterOp { #[pymethods] impl PyNodeFilterOp { fn __eq__(&self, value: String) -> PyFilterExpr { - PyFilterExpr(self.0.eq(value)) + self.0.eq(value) } fn __ne__(&self, value: String) -> PyFilterExpr { - PyFilterExpr(self.0.ne(value)) + self.0.ne(value) } - fn includes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.includes(values)) + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + self.0.is_in(values.into()) } - fn excludes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.excludes(values)) + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + self.0.is_not_in(values.into()) + } + + 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( @@ -186,10 +368,8 @@ impl PyNodeFilterOp { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr( - self.0 - .fuzzy_search(value, levenshtein_distance, prefix_match), - ) + self.0 + .fuzzy_search(value, levenshtein_distance, prefix_match) } } @@ -200,26 +380,21 @@ pub struct PyNodeFilter; #[pymethods] impl PyNodeFilter { #[staticmethod] - fn node_name() -> PyNodeFilterOp { - PyNodeFilterOp(Arc::new(NodeFilter::node_name())) + fn name() -> PyNodeFilterOp { + PyNodeFilterOp(Arc::new(NodeFilter::name())) } #[staticmethod] fn node_type() -> PyNodeFilterOp { PyNodeFilterOp(Arc::new(NodeFilter::node_type())) } - - #[staticmethod] - fn property(name: String) -> PropertyFilterBuilder { - PropertyFilterBuilder(name) - } } #[pyclass(frozen, name = "EdgeFilterOp", module = "raphtory.filter")] #[derive(Clone)] -pub struct PyEdgeFilterOp(Arc); +pub struct PyEdgeFilterOp(Arc); -impl From for PyEdgeFilterOp { +impl From for PyEdgeFilterOp { fn from(value: T) -> Self { PyEdgeFilterOp(Arc::new(value)) } @@ -228,19 +403,33 @@ impl From for PyEdgeFilterOp { #[pymethods] impl PyEdgeFilterOp { fn __eq__(&self, value: String) -> PyFilterExpr { - PyFilterExpr(self.0.eq(value)) + let field = self.0.eq(value); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) } fn __ne__(&self, value: String) -> PyFilterExpr { - PyFilterExpr(self.0.ne(value)) + let field = self.0.ne(value); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) } - fn includes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.includes(values)) + fn is_in(&self, values: FromIterable) -> PyFilterExpr { + let field = self.0.is_in(values); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) } - fn excludes(&self, values: Vec) -> PyFilterExpr { - PyFilterExpr(self.0.excludes(values)) + fn is_not_in(&self, values: FromIterable) -> PyFilterExpr { + let field = self.0.is_not_in(values); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + let field = self.0.contains(value); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + let field = self.0.not_contains(value); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) } fn fuzzy_search( @@ -249,10 +438,21 @@ impl PyEdgeFilterOp { levenshtein_distance: usize, prefix_match: bool, ) -> PyFilterExpr { - PyFilterExpr( - self.0 - .fuzzy_search(value, levenshtein_distance, prefix_match), - ) + let field = self + .0 + .fuzzy_search(value, levenshtein_distance, prefix_match); + PyFilterExpr(PyInnerFilterExpr::Edge(Arc::new(field))) + } +} + +#[pyclass(frozen, name = "EdgeEndpoint", module = "raphtory.filter")] +#[derive(Clone)] +pub struct PyEdgeEndpoint(pub EdgeEndpointFilter); + +#[pymethods] +impl PyEdgeEndpoint { + fn name(&self) -> PyEdgeFilterOp { + PyEdgeFilterOp(self.0.name()) } } @@ -263,31 +463,33 @@ pub struct PyEdgeFilter; #[pymethods] impl PyEdgeFilter { #[staticmethod] - fn src() -> PyEdgeFilterOp { - PyEdgeFilterOp(Arc::new(EdgeFilter::src())) + fn src() -> PyEdgeEndpoint { + PyEdgeEndpoint(EdgeFilter::src()) } #[staticmethod] - fn dst() -> PyEdgeFilterOp { - PyEdgeFilterOp(Arc::new(EdgeFilter::dst())) + fn dst() -> PyEdgeEndpoint { + PyEdgeEndpoint(EdgeFilter::dst()) } +} - #[staticmethod] - fn property(name: String) -> PropertyFilterBuilder { - PropertyFilterBuilder(name) - } +#[pyfunction(name = "Property")] +fn property(name: String) -> PropertyFilterBuilder { + PropertyFilterBuilder(name) } pub fn base_filter_module(py: Python<'_>) -> Result, PyErr> { let filter_module = PyModule::new(py, "filter")?; - filter_module.add_class::()?; filter_module.add_class::()?; filter_module.add_class::()?; filter_module.add_class::()?; + filter_module.add_class::()?; filter_module.add_class::()?; filter_module.add_class::()?; filter_module.add_class::()?; + filter_module.add_function(wrap_pyfunction!(property, filter_module.clone())?)?; + Ok(filter_module) } diff --git a/raphtory/src/python/types/wrappers/prop.rs b/raphtory/src/python/types/wrappers/prop.rs index 02abc2f214..5692891a75 100644 --- a/raphtory/src/python/types/wrappers/prop.rs +++ b/raphtory/src/python/types/wrappers/prop.rs @@ -1,13 +1,23 @@ use crate::{ core::{prop_array::PropArray, utils::errors::GraphError, Prop}, - db::graph::views::property_filter::{ - internal::{ - InternalEdgeFilterOps, InternalExplodedEdgeFilterOps, InternalNodePropertyFilterOps, + db::{ + api::view::{BoxableGraphView, DynamicGraph, IntoDynamic}, + graph::views::filter::{ + internal::{ + InternalEdgeFilterOps, + InternalNodeFilterOps, // InternalExplodedEdgeFilterOps, , + }, + model::{ + property_filter::PropertyRef, AsEdgeFilter, AsNodeFilter, + InternalNodeFilterBuilderOps, NodeFilterBuilderOps, + }, }, - PropertyRef, }, prelude::{GraphViewOps, PropertyFilter}, - python::types::repr::Repr, + python::types::{ + repr::Repr, + wrappers::filter_expr::{PyFilterExpr, PyInnerFilterExpr}, + }, }; use bigdecimal::BigDecimal; use pyo3::{ @@ -161,33 +171,202 @@ impl InternalEdgeFilterOps for PyPropertyFilter { } } -impl InternalExplodedEdgeFilterOps for PyPropertyFilter { - type ExplodedEdgeFiltered<'graph, G> - = ::ExplodedEdgeFiltered<'graph, G> +// impl InternalExplodedEdgeFilterOps for PyPropertyFilter { +// type ExplodedEdgeFiltered<'graph, G> +// = ::ExplodedEdgeFiltered<'graph, G> +// where +// G: GraphViewOps<'graph>, +// Self: 'graph; +// +// fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( +// self, +// graph: G, +// ) -> Result, GraphError> { +// self.0.create_exploded_edge_filter(graph) +// } +// } + +impl InternalNodeFilterOps for PyPropertyFilter { + type NodeFiltered<'graph, G> + = ::NodeFiltered<'graph, G> + where + Self: 'graph, + G: GraphViewOps<'graph>; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + self.0.create_node_filter(graph) + } +} + +impl InternalNodeFilterOps for PyFilterExpr { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> + = Arc where - G: GraphViewOps<'graph>, Self: 'graph; - fn create_exploded_edge_filter<'graph, G: GraphViewOps<'graph>>( + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, - ) -> Result, GraphError> { - self.0.create_exploded_edge_filter(graph) + ) -> Result, GraphError> { + match self.0 { + PyInnerFilterExpr::Node(i) => i.create_node_filter(graph), + PyInnerFilterExpr::Property(i) => i.create_node_filter(graph), + PyInnerFilterExpr::Edge(_) => Err(GraphError::ParsingError), + } + } +} + +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 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 DynNodeFilterBuilderOps for T +where + T: InternalNodeFilterBuilderOps, +{ + fn eq(&self, value: String) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(NodeFilterBuilderOps::eq( + self, value, + )))) + } + + fn ne(&self, value: String) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new(NodeFilterBuilderOps::ne( + self, value, + )))) + } + + fn is_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NodeFilterBuilderOps::is_in(self, values), + ))) + } + + fn is_not_in(&self, values: Vec) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NodeFilterBuilderOps::is_not_in(self, values), + ))) + } + + fn contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NodeFilterBuilderOps::contains(self, value), + ))) + } + + fn not_contains(&self, value: String) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NodeFilterBuilderOps::not_contains(self, value), + ))) + } + + fn fuzzy_search( + &self, + value: String, + levenshtein_distance: usize, + prefix_match: bool, + ) -> PyFilterExpr { + PyFilterExpr(PyInnerFilterExpr::Node(Arc::new( + NodeFilterBuilderOps::fuzzy_search(self, value, levenshtein_distance, prefix_match), + ))) } } -impl InternalNodePropertyFilterOps for PyPropertyFilter { - type NodePropertyFiltered<'graph, G> - = ::NodePropertyFiltered<'graph, G> +pub trait DynInternalNodeFilterOps: AsNodeFilter { + fn create_dyn_node_filter<'graph>( + &self, + graph: Arc, + ) -> Result, GraphError>; +} + +impl DynInternalNodeFilterOps for T { + fn create_dyn_node_filter<'graph>( + &self, + graph: Arc, + ) -> Result, GraphError> { + Ok(Arc::new(self.clone().create_node_filter(graph)?)) + } +} + +impl InternalNodeFilterOps for Arc { + type NodeFiltered<'graph, G: GraphViewOps<'graph>> + = Arc where - Self: 'graph, - G: GraphViewOps<'graph>; + Self: 'graph; + + fn create_node_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + self.deref().create_dyn_node_filter(Arc::new(graph)) + } +} + +impl InternalEdgeFilterOps for PyFilterExpr { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> + = Arc + where + Self: 'graph; - fn create_node_property_filter<'graph, G: GraphViewOps<'graph>>( + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( self, graph: G, - ) -> Result, GraphError> { - self.0.create_node_property_filter(graph) + ) -> Result, GraphError> { + match self.0 { + PyInnerFilterExpr::Edge(i) => i.create_edge_filter(graph), + PyInnerFilterExpr::Property(i) => i.create_edge_filter(graph), + PyInnerFilterExpr::Node(_) => Err(GraphError::ParsingError), + } + } +} + +pub trait DynInternalEdgeFilterOps: AsEdgeFilter { + fn create_dyn_edge_filter<'graph>( + &self, + graph: Arc, + ) -> Result, GraphError>; +} + +impl DynInternalEdgeFilterOps for T { + fn create_dyn_edge_filter<'graph>( + &self, + graph: Arc, + ) -> Result, GraphError> { + Ok(Arc::new(self.clone().create_edge_filter(graph)?)) + } +} + +impl InternalEdgeFilterOps for Arc { + type EdgeFiltered<'graph, G: GraphViewOps<'graph>> + = Arc + where + Self: 'graph; + + fn create_edge_filter<'graph, G: GraphViewOps<'graph>>( + self, + graph: G, + ) -> Result, GraphError> { + self.deref().create_dyn_edge_filter(Arc::new(graph)) } } @@ -261,6 +440,24 @@ impl PyPropertyRef { PyPropertyFilter(filter) } + /// Create a filter that keeps entities that contains the property + /// + /// Returns: + /// PropertyFilter: the property filter + fn contains(&self, value: Prop) -> PyPropertyFilter { + let filter = PropertyFilter::contains(PropertyRef::Property(self.name.clone()), value); + PyPropertyFilter(filter) + } + + /// Create a filter that keeps entities that do not contain the property + /// + /// Returns: + /// PropertyFilter: the property filter + fn not_contains(&self, value: Prop) -> PyPropertyFilter { + let filter = PropertyFilter::not_contains(PropertyRef::Property(self.name.clone()), value); + PyPropertyFilter(filter) + } + /// Create a filter that keeps entities if their property value is in the set /// /// Arguments: @@ -268,8 +465,8 @@ impl PyPropertyRef { /// /// Returns: /// PropertyFilter: the property filter - fn any(&self, values: HashSet) -> PyPropertyFilter { - let filter = PropertyFilter::includes(PropertyRef::Property(self.name.clone()), values); + fn is_in(&self, values: HashSet) -> PyPropertyFilter { + let filter = PropertyFilter::is_in(PropertyRef::Property(self.name.clone()), values); PyPropertyFilter(filter) } @@ -281,8 +478,8 @@ impl PyPropertyRef { /// /// Returns: /// PropertyFilter: the property filter - fn not_any(&self, values: HashSet) -> PyPropertyFilter { - let filter = PropertyFilter::excludes(PropertyRef::Property(self.name.clone()), values); + fn is_not_in(&self, values: HashSet) -> PyPropertyFilter { + let filter = PropertyFilter::is_not_in(PropertyRef::Property(self.name.clone()), values); PyPropertyFilter(filter) } } diff --git a/raphtory/src/search/edge_filter_executor.rs b/raphtory/src/search/edge_filter_executor.rs index d291ae2f05..6f87ee3862 100644 --- a/raphtory/src/search/edge_filter_executor.rs +++ b/raphtory/src/search/edge_filter_executor.rs @@ -4,12 +4,17 @@ use crate::{ api::{storage::graph::edges::edge_storage_ops::EdgeStorageOps, view::StaticGraphViewOps}, graph::{ edge::EdgeView, - views::property_filter::{ - CompositeEdgeFilter, Filter, FilterOperator, PropertyRef, Temporal, + views::filter::{ + internal::InternalEdgeFilterOps, + model::{ + edge_filter::{CompositeEdgeFilter, EdgeFieldFilter}, + property_filter::{PropertyRef, Temporal}, + Filter, + }, }, }, }, - prelude::{EdgeViewOps, GraphViewOps, PropertyFilter}, + prelude::{EdgePropertyFilterOps, EdgeViewOps, GraphViewOps, PropertyFilter}, search::{ collectors::{ edge_property_filter_collector::EdgePropertyFilterCollector, @@ -299,7 +304,9 @@ impl<'a> EdgeFilterExecutor<'a> { limit, offset, )?, - None => vec![], + None => { + Self::raph_filter_edges(graph, &EdgeFieldFilter(filter.clone()), offset, limit)? + } }; Ok(results) @@ -319,36 +326,34 @@ impl<'a> EdgeFilterExecutor<'a> { CompositeEdgeFilter::Edge(filter) => { self.filter_edge_index(graph, filter, limit, offset) } - CompositeEdgeFilter::And(filters) => { - let mut results = None; - - for sub_filter in filters { - let sub_result = self.filter_edges(graph, sub_filter, limit, offset)?; - results = Some( - results - .map(|r: Vec<_>| { - r.into_iter() - .filter(|item| sub_result.contains(item)) - .collect::>() // Ensure intersection results stay in a Vec - }) - .unwrap_or(sub_result), - ); - } + CompositeEdgeFilter::And(left, right) => { + let left_result = self.filter_edges(graph, left, limit, offset)?; + let right_result = self.filter_edges(graph, right, limit, offset)?; + + // Intersect results + let left_set: HashSet<_> = left_result.into_iter().collect(); + let intersection = right_result + .into_iter() + .filter(|e| left_set.contains(e)) + .collect::>(); - Ok(results.unwrap_or_default()) + Ok(intersection) } - CompositeEdgeFilter::Or(filters) => { - let mut results = HashSet::new(); + CompositeEdgeFilter::Or(left, right) => { + let left_result = self.filter_edges(graph, left, limit, offset)?; + let right_result = self.filter_edges(graph, right, limit, offset)?; - for sub_filter in filters { - let sub_result = self.filter_edges(graph, sub_filter, limit, offset)?; - results.extend(sub_result); - } + // Union results + let mut combined = HashSet::new(); + combined.extend(left_result); + combined.extend(right_result); - Ok(results.into_iter().collect()) + Ok(combined.into_iter().collect()) } + CompositeEdgeFilter::Not(_) => Self::raph_filter_edges(graph, filter, offset, limit), } } + pub fn filter_edges( &self, graph: &G, @@ -404,36 +409,19 @@ impl<'a> EdgeFilterExecutor<'a> { fn raph_filter_edges( graph: &G, - filter: &PropertyFilter, - limit: usize, + filter: &(impl InternalEdgeFilterOps + Clone), offset: usize, + limit: usize, ) -> Result>, GraphError> { - match filter.operator { - FilterOperator::IsNone => Ok(match &filter.prop_ref { - PropertyRef::Property(prop_name) => graph - .edges() - .into_iter() - .filter(|e| e.properties().get(prop_name).is_none()) - .skip(offset) - .take(limit) - .collect::>(), - PropertyRef::ConstantProperty(prop_name) => graph - .edges() - .into_iter() - .filter(|e| e.properties().constant().get(prop_name).is_none()) - .skip(offset) - .take(limit) - .collect::>(), - PropertyRef::TemporalProperty(prop_name, _) => graph - .edges() - .into_iter() - .filter(|e| e.properties().temporal().get(prop_name).is_none()) - .skip(offset) - .take(limit) - .collect::>(), - }), - _ => Err(GraphError::NotSupported), - } + let filtered_edges = graph + .filter_edges(filter.clone())? + .edges() + .iter() + .map(|e| EdgeView::new(graph.clone(), e.edge)) + .skip(offset) + .take(limit) + .collect(); + Ok(filtered_edges) } #[allow(dead_code)] diff --git a/raphtory/src/search/edge_index.rs b/raphtory/src/search/edge_index.rs index b235ee4426..2fb934fa19 100644 --- a/raphtory/src/search/edge_index.rs +++ b/raphtory/src/search/edge_index.rs @@ -15,19 +15,25 @@ use crate::{ prelude::*, search::{ entity_index::EntityIndex, - fields::{DESTINATION, EDGE_ID, SOURCE}, + fields::{ + DESTINATION, DESTINATION_TOKENIZED, EDGE_ID, NODE_ID, NODE_NAME, NODE_NAME_TOKENIZED, + NODE_TYPE, NODE_TYPE_TOKENIZED, SOURCE, SOURCE_TOKENIZED, + }, TOKENIZER, }, }; use raphtory_api::core::storage::dict_mapper::MaybeNew; use rayon::prelude::ParallelIterator; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + path::{Path, PathBuf}, +}; use tantivy::{ collector::TopDocs, query::AllQuery, schema::{ Field, IndexRecordOption, Schema, SchemaBuilder, TextFieldIndexing, TextOptions, FAST, - INDEXED, STORED, + INDEXED, STORED, STRING, }, Document, IndexWriter, TantivyDocument, TantivyError, }; @@ -36,8 +42,10 @@ use tantivy::{ pub struct EdgeIndex { pub(crate) entity_index: EntityIndex, pub(crate) edge_id_field: Field, - pub(crate) from_field: Field, - pub(crate) to_field: Field, + pub(crate) src_field: Field, + pub(crate) src_tokenized_field: Field, + pub(crate) dst_field: Field, + pub(crate) dst_tokenized_field: Field, } impl Debug for EdgeIndex { @@ -49,21 +57,67 @@ impl Debug for EdgeIndex { } impl EdgeIndex { - pub(crate) fn new() -> Self { + fn fetch_fields(schema: &Schema) -> Result<(Field, Field, Field, Field, Field), GraphError> { + let edge_id_field = schema + .get_field(EDGE_ID) + .map_err(|_| GraphError::IndexErrorMsg("Edge ID field missing in schema.".into()))?; + + let src_field = schema + .get_field(SOURCE) + .map_err(|_| GraphError::IndexErrorMsg("Source field missing in schema.".into()))?; + + let src_tokenized_field = schema.get_field(SOURCE_TOKENIZED).map_err(|_| { + GraphError::IndexErrorMsg("Tokenized source field missing in schema.".into()) + })?; + + let dst_field = schema.get_field(DESTINATION).map_err(|_| { + GraphError::IndexErrorMsg("Destination field missing in schema.".into()) + })?; + + let dst_tokenized_field = schema.get_field(DESTINATION_TOKENIZED).map_err(|_| { + GraphError::IndexErrorMsg("Tokenized destination field missing in schema.".into()) + })?; + + Ok(( + edge_id_field, + src_field, + src_tokenized_field, + dst_field, + dst_tokenized_field, + )) + } + + pub(crate) fn new(path: &Option) -> Result { let schema = Self::schema_builder().build(); - let edge_id_field = schema.get_field(EDGE_ID).ok().expect("Edge ID is absent"); - let from_field = schema.get_field(SOURCE).expect("Source is absent"); - let to_field = schema - .get_field(DESTINATION) - .expect("Destination is absent"); - - let entity_index = EntityIndex::new(schema); - EdgeIndex { + let (edge_id_field, src_field, src_tokenized_field, dst_field, dst_tokenized_field) = + Self::fetch_fields(&schema)?; + + let entity_index = EntityIndex::new(schema, path)?; + + Ok(Self { entity_index, edge_id_field, - from_field, - to_field, - } + src_field, + src_tokenized_field, + dst_field, + dst_tokenized_field, + }) + } + + pub(crate) fn load_from_path(path: &PathBuf) -> Result { + let entity_index = EntityIndex::load_edges_index_from_path(path)?; + let schema = entity_index.index.schema(); + let (edge_id_field, src_field, src_tokenized_field, dst_field, dst_tokenized_field) = + Self::fetch_fields(&schema)?; + + Ok(Self { + entity_index, + edge_id_field, + src_field, + src_tokenized_field, + dst_field, + dst_tokenized_field, + }) } pub(crate) fn print(&self) -> Result<(), GraphError> { @@ -78,13 +132,11 @@ impl EdgeIndex { } let constant_property_indexes = self.entity_index.const_property_indexes.read(); - for property_index in constant_property_indexes.iter().flatten() { property_index.print()?; } let temporal_property_indexes = self.entity_index.temporal_property_indexes.read(); - for property_index in temporal_property_indexes.iter().flatten() { property_index.print()?; } @@ -95,16 +147,18 @@ impl EdgeIndex { fn schema_builder() -> SchemaBuilder { let mut schema_builder = Schema::builder(); schema_builder.add_u64_field(EDGE_ID, INDEXED | FAST | STORED); + schema_builder.add_text_field(SOURCE, STRING); schema_builder.add_text_field( - SOURCE, + SOURCE_TOKENIZED, TextOptions::default().set_indexing_options( TextFieldIndexing::default() .set_tokenizer(TOKENIZER) .set_index_option(IndexRecordOption::WithFreqsAndPositions), ), ); + schema_builder.add_text_field(DESTINATION, STRING); schema_builder.add_text_field( - DESTINATION, + DESTINATION_TOKENIZED, TextOptions::default().set_indexing_options( TextFieldIndexing::default() .set_tokenizer(TOKENIZER) @@ -118,11 +172,20 @@ impl EdgeIndex { self.entity_index.index.schema().get_field(field_name) } + pub fn get_tokenized_edge_field(&self, field_name: &str) -> tantivy::Result { + self.entity_index + .index + .schema() + .get_field(format!("{field_name}_tokenized").as_ref()) + } + fn create_document<'a>(&self, edge_id: u64, src: String, dst: String) -> TantivyDocument { let mut document = TantivyDocument::new(); document.add_u64(self.edge_id_field, edge_id); - document.add_text(self.from_field, src); - document.add_text(self.to_field, dst); + document.add_text(self.src_field, src.clone()); + document.add_text(self.src_tokenized_field, src); + document.add_text(self.dst_field, dst.clone()); + document.add_text(self.dst_tokenized_field, dst); document } @@ -247,23 +310,41 @@ impl EdgeIndex { Ok(()) } - pub(crate) fn index_edges(graph: &GraphStorage) -> Result { - let edge_index = EdgeIndex::new(); + pub(crate) fn index_edges( + graph: &GraphStorage, + path: Option<&Path>, + ) -> Result { + let edge_index_path = path.as_deref().map(|p| p.join("edges")); + let edge_index = EdgeIndex::new(&edge_index_path)?; // Initialize property indexes and get their writers let const_property_keys = graph.edge_meta().const_prop_meta().get_keys().into_iter(); + let const_properties_index_path = edge_index_path + .as_deref() + .map(|p| p.join("const_properties")); let mut const_writers = edge_index .entity_index - .initialize_edge_const_property_indexes(graph, const_property_keys)?; + .initialize_edge_const_property_indexes( + graph, + const_property_keys, + &const_properties_index_path, + )?; let temporal_property_keys = graph .edge_meta() .temporal_prop_meta() .get_keys() .into_iter(); + let temporal_properties_index_path = edge_index_path + .as_deref() + .map(|p| p.join("temporal_properties")); let mut temporal_writers = edge_index .entity_index - .initialize_edge_temporal_property_indexes(graph, temporal_property_keys)?; + .initialize_edge_temporal_property_indexes( + graph, + temporal_property_keys, + &temporal_properties_index_path, + )?; let mut writer = edge_index.entity_index.index.writer(100_000_000)?; let locked_g = graph.core_graph(); diff --git a/raphtory/src/search/entity_index.rs b/raphtory/src/search/entity_index.rs index beaea80e3c..553996f471 100644 --- a/raphtory/src/search/entity_index.rs +++ b/raphtory/src/search/entity_index.rs @@ -1,7 +1,7 @@ use crate::{ core::{utils::errors::GraphError, Prop}, db::api::storage::graph::storage_ops::GraphStorage, - search::{fields, new_index, property_index::PropertyIndex}, + search::{fields, new_index, property_index::PropertyIndex, register_default_tokenizers}, }; use itertools::Itertools; use parking_lot::RwLock; @@ -10,7 +10,7 @@ use raphtory_api::core::{ storage::{arc_str::ArcStr, dict_mapper::MaybeNew, timeindex::TimeIndexEntry}, PropType, }; -use std::{borrow::Borrow, sync::Arc}; +use std::{borrow::Borrow, path::PathBuf, sync::Arc}; use tantivy::{ schema::{Schema, SchemaBuilder, FAST, INDEXED, STORED}, Index, IndexReader, IndexWriter, Term, @@ -25,14 +25,46 @@ pub struct EntityIndex { } impl EntityIndex { - pub(crate) fn new(schema: Schema) -> Self { - let (index, reader) = new_index(schema); - Self { + pub(crate) fn new(schema: Schema, path: &Option) -> Result { + let path = path.as_ref().map(|p| p.join("fields")); + let (index, reader) = new_index(schema, &path)?; + Ok(Self { index: Arc::new(index), reader, const_property_indexes: Arc::new(RwLock::new(Vec::new())), temporal_property_indexes: Arc::new(RwLock::new(Vec::new())), - } + }) + } + + fn load_from_path(path: &PathBuf, is_edge: bool) -> Result { + let index = Index::open_in_dir(path.join("fields"))?; + + register_default_tokenizers(&index); + + let reader = index + .reader_builder() + .reload_policy(tantivy::ReloadPolicy::Manual) + .try_into()?; + + let const_property_indexes = + PropertyIndex::load_all(&path.join("const_properties"), is_edge)?; + let temporal_property_indexes = + PropertyIndex::load_all(&path.join("temporal_properties"), is_edge)?; + + Ok(Self { + index: Arc::new(index), + reader, + const_property_indexes: Arc::new(RwLock::new(const_property_indexes)), + temporal_property_indexes: Arc::new(RwLock::new(temporal_property_indexes)), + }) + } + + pub(crate) fn load_nodes_index_from_path(path: &PathBuf) -> Result { + EntityIndex::load_from_path(path, false) + } + + pub(crate) fn load_edges_index_from_path(path: &PathBuf) -> Result { + EntityIndex::load_from_path(path, true) } pub(crate) fn create_property_index( @@ -43,7 +75,8 @@ impl EntityIndex { is_static: bool, add_const_schema_fields: fn(&mut SchemaBuilder), add_temporal_schema_fields: fn(&mut SchemaBuilder), - new_property: fn(Schema) -> PropertyIndex, + new_property: fn(Schema, path: &Option) -> Result, + path: &Option, ) -> Result<(), GraphError> { prop_id .if_new(|prop_id| { @@ -60,14 +93,20 @@ impl EntityIndex { let mut schema_builder = PropertyIndex::schema_builder(&*prop_name, prop_type.clone()); - if is_static { + + let path = if is_static { add_const_schema_fields(&mut schema_builder); + path.as_deref().map(|p| p.join("const_properties")) } else { add_temporal_schema_fields(&mut schema_builder); - } + path.as_deref().map(|p| p.join("temporal_properties")) + }; + let schema = schema_builder.build(); - let property_index = new_property(schema); + let prop_index_path = path.map(|p| p.join(prop_id.to_string())); + let property_index = new_property(schema, &prop_index_path)?; prop_index_guard[prop_id] = Some(property_index); + Ok::<_, GraphError>(()) }) .transpose()?; @@ -118,7 +157,8 @@ impl EntityIndex { prop_keys: impl Iterator, get_property_meta: fn(&GraphStorage) -> &PropMapper, add_schema_fields: fn(&mut SchemaBuilder), - new_property: fn(Schema) -> PropertyIndex, + new_property: fn(Schema, &Option) -> Result, + path: &Option, ) -> Result>, GraphError> { let prop_meta = get_property_meta(graph); let properties = prop_keys @@ -145,7 +185,8 @@ impl EntityIndex { let mut schema_builder = PropertyIndex::schema_builder(&*prop_name, prop_type); add_schema_fields(&mut schema_builder); let schema = schema_builder.build(); - let property_index = new_property(schema); + let prop_index_path = path.as_deref().map(|p| p.join(prop_id.to_string())); + let property_index = new_property(schema, &prop_index_path)?; let writer = property_index.index.writer(50_000_000)?; writers.push(Some(writer)); @@ -160,6 +201,7 @@ impl EntityIndex { &self, graph: &GraphStorage, prop_keys: impl Iterator, + path: &Option, ) -> Result>, GraphError> { self.initialize_property_indexes( graph, @@ -170,6 +212,7 @@ impl EntityIndex { schema.add_u64_field(fields::NODE_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_node_property, + path, ) } @@ -177,6 +220,7 @@ impl EntityIndex { &self, graph: &GraphStorage, prop_keys: impl Iterator, + path: &Option, ) -> Result>, GraphError> { self.initialize_property_indexes( graph, @@ -189,6 +233,7 @@ impl EntityIndex { schema.add_u64_field(fields::NODE_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_node_property, + path, ) } @@ -196,6 +241,7 @@ impl EntityIndex { &self, graph: &GraphStorage, prop_keys: impl Iterator, + path: &Option, ) -> Result>, GraphError> { self.initialize_property_indexes( graph, @@ -207,6 +253,7 @@ impl EntityIndex { schema.add_u64_field(fields::LAYER_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_edge_property, + path, ) } @@ -214,6 +261,7 @@ impl EntityIndex { &self, graph: &GraphStorage, prop_keys: impl Iterator, + path: &Option, ) -> Result>, GraphError> { self.initialize_property_indexes( graph, @@ -227,6 +275,7 @@ impl EntityIndex { schema.add_u64_field(fields::LAYER_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_edge_property, + path, ) } diff --git a/raphtory/src/search/graph_index.rs b/raphtory/src/search/graph_index.rs index edf46af807..98c25d2911 100644 --- a/raphtory/src/search/graph_index.rs +++ b/raphtory/src/search/graph_index.rs @@ -4,7 +4,7 @@ use crate::{ storage::timeindex::TimeIndexEntry, utils::errors::GraphError, }, - db::api::storage::graph::storage_ops::GraphStorage, + db::api::{storage::graph::storage_ops::GraphStorage, view::internal::InternalStorageOps}, prelude::*, search::{ edge_index::EdgeIndex, fields, node_index::NodeIndex, property_index::PropertyIndex, @@ -12,13 +12,25 @@ use crate::{ }, }; use raphtory_api::core::{storage::dict_mapper::MaybeNew, PropType}; -use std::fmt::{Debug, Formatter}; +use std::{ + ffi::OsStr, + fmt::{Debug, Formatter}, + fs, + fs::File, + path::{Path, PathBuf}, + sync::Arc, +}; use tantivy::schema::{FAST, INDEXED, STORED}; +use tempfile::TempDir; +use uuid::Uuid; +use walkdir::WalkDir; +use zip::{write::FileOptions, ZipArchive, ZipWriter}; #[derive(Clone)] pub struct GraphIndex { pub(crate) node_index: NodeIndex, pub(crate) edge_index: EdgeIndex, + pub path: Option>, // If path is None, index is created in-memory } impl Debug for GraphIndex { @@ -30,29 +42,140 @@ impl Debug for GraphIndex { } } -impl<'a> TryFrom<&'a GraphStorage> for GraphIndex { - type Error = GraphError; +impl GraphIndex { + fn copy_dir_recursive(source: &Path, destination: &Path) -> Result<(), GraphError> { + for entry in WalkDir::new(source) { + let entry = entry.map_err(|e| { + GraphError::IOErrorMsg(format!("Failed to read directory entry: {}", e)) + })?; + + let entry_path = entry.path(); + + if entry_path.starts_with(destination) { + continue; + } + + let relative_path = entry_path.strip_prefix(source).map_err(|e| { + GraphError::IOErrorMsg(format!( + "Failed to determine relative path during copy: {}", + e + )) + })?; + + let dest_path = destination.join(relative_path); + + if entry_path.is_dir() { + fs::create_dir_all(&dest_path).map_err(|e| { + GraphError::IOErrorMsg(format!( + "Failed to create directory {}: {}", + dest_path.display(), + e + )) + })?; + } else if entry_path.is_file() { + if let Some(parent) = dest_path.parent() { + fs::create_dir_all(parent).map_err(|e| { + GraphError::IOErrorMsg(format!( + "Failed to create parent directory {}: {}", + parent.display(), + e + )) + })?; + } + + fs::copy(entry_path, &dest_path).map_err(|e| { + GraphError::IOErrorMsg(format!( + "Failed to copy file {} to {}: {}", + entry_path.display(), + dest_path.display(), + e + )) + })?; + } + } - fn try_from(graph: &GraphStorage) -> Result { - let node_index = NodeIndex::index_nodes(graph)?; - // node_index.print()?; + Ok(()) + } - let edge_index = EdgeIndex::index_edges(graph)?; - // edge_index.print()?; + fn unzip_index(source: &Path, destination: &Path) -> Result<(), GraphError> { + let file = File::open(source)?; + let mut archive = ZipArchive::new(file)?; + + for i in 0..archive.len() { + let mut entry = archive.by_index(i)?; + let entry_path = Path::new(entry.name()); + + // Check if the first component is "index" + if entry_path.components().next().map(|c| c.as_os_str()) != Some(OsStr::new("index")) { + continue; + } + + // Strip "index" from the path + let rel_path = entry_path.strip_prefix("index").map_err(|e| { + GraphError::IOErrorMsg(format!("Failed to strip 'index' prefix: {}", e)) + })?; + + let out_path = destination.join(rel_path); + + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent)?; + } + + let mut outfile = File::create(&out_path)?; + std::io::copy(&mut entry, &mut outfile)?; + } + + Ok(()) + } + + pub fn load_from_path(path: &PathBuf) -> Result { + let tmp_path = TempDir::new_in(path)?; + if path.is_file() { + GraphIndex::unzip_index(path, tmp_path.path())?; + } else { + GraphIndex::copy_dir_recursive(path, tmp_path.path())?; + } + + let node_index = NodeIndex::load_from_path(&tmp_path.path().join("nodes"))?; + let edge_index = EdgeIndex::load_from_path(&tmp_path.path().join("edges"))?; + let path = Some(Arc::new(tmp_path)); Ok(GraphIndex { node_index, edge_index, + path, }) } -} -impl GraphIndex { - pub fn new() -> Self { - GraphIndex { - node_index: NodeIndex::new(), - edge_index: EdgeIndex::new(), - } + pub fn create_from_graph( + graph: &GraphStorage, + create_in_ram: bool, + cache_path: Option<&Path>, + ) -> Result { + let dir = if !create_in_ram { + let temp_dir = match cache_path { + // Creates index in a temp dir within cache graph dir. + // The intention is to avoid creating index in a tmp dir that could be on another file system. + Some(path) => TempDir::new_in(path)?, + None => TempDir::new()?, + }; + Some(Arc::new(temp_dir)) + } else { + None + }; + + let path = dir.as_ref().map(|p| p.path()); + let node_index = NodeIndex::index_nodes(graph, path)?; + // node_index.print()?; + + let edge_index = EdgeIndex::index_edges(graph, path)?; + // edge_index.print()?; + + Ok(GraphIndex { + node_index, + edge_index, + path: dir, + }) } pub fn searcher(&self) -> Searcher { @@ -67,6 +190,77 @@ impl GraphIndex { Ok(()) } + pub(crate) fn persist_to_disk(&self, path: &Path) -> Result<(), GraphError> { + let source_path = self.path.as_ref().ok_or(GraphError::GraphIndexIsMissing)?; + + let temp_path = &path.with_extension(format!("tmp-{}", Uuid::new_v4())); + + GraphIndex::copy_dir_recursive(source_path.path(), temp_path)?; + + // Always overwrite the existing graph index when persisting, since the in-memory + // working index may have newer updates. The persisted index is decoupled from the + // active one, and changes remain in memory unless explicitly saved. + // This behavior mirrors how the in-memory graph works — updates are not persisted + // unless manually saved, except when using the cached view (see db/graph/views/cached_view). + if path.exists() { + fs::remove_dir_all(path) + .map_err(|_e| GraphError::FailedToRemoveExistingGraphIndex(path.to_path_buf()))?; + } + + fs::rename(temp_path, path).map_err(|e| { + GraphError::IOErrorMsg(format!("Failed to rename temp index folder: {}", e)) + })?; + + Ok(()) + } + + pub(crate) fn persist_to_disk_zip(&self, path: &Path) -> Result<(), GraphError> { + let index_path = &path.join("index"); + + let source_path = self.path.as_ref().ok_or(GraphError::GraphIndexIsMissing)?; + + if index_path.exists() { + fs::remove_dir_all(index_path) + .map_err(|_e| GraphError::FailedToRemoveExistingGraphIndex(index_path.clone()))?; + } + + let file = File::options().read(true).write(true).open(path)?; + + let mut zip = ZipWriter::new_append(file)?; + + for entry in WalkDir::new(&source_path.path()) + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.path().is_file()) + { + let rel_path = entry + .path() + .strip_prefix(source_path.path()) + .map_err(|e| GraphError::IOErrorMsg(format!("Failed to strip path: {}", e)))?; + + let zip_entry_name = PathBuf::from("index") + .join(rel_path) + .to_string_lossy() + .into_owned(); + zip.start_file::<_, ()>(zip_entry_name, FileOptions::default()) + .map_err(|e| { + GraphError::IOErrorMsg(format!("Failed to start zip file entry: {}", e)) + })?; + + let mut f = File::open(entry.path()) + .map_err(|e| GraphError::IOErrorMsg(format!("Failed to open index file: {}", e)))?; + + std::io::copy(&mut f, &mut zip).map_err(|e| { + GraphError::IOErrorMsg(format!("Failed to write zip content: {}", e)) + })?; + } + + zip.finish() + .map_err(|e| GraphError::IOErrorMsg(format!("Failed to finalize zip: {}", e)))?; + + Ok(()) + } + pub(crate) fn add_node_update( &self, graph: &GraphStorage, @@ -140,6 +334,7 @@ impl GraphIndex { prop_type: &PropType, is_static: bool, // Const or Temporal Property ) -> Result<(), GraphError> { + let edge_index_path = self.path.as_deref().map(|p| p.path().join("edges")); self.edge_index.entity_index.create_property_index( prop_id, prop_name, @@ -156,6 +351,7 @@ impl GraphIndex { schema.add_u64_field(fields::LAYER_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_edge_property, + &edge_index_path, ) } @@ -166,6 +362,7 @@ impl GraphIndex { prop_type: &PropType, is_static: bool, // Const or Temporal Property ) -> Result<(), GraphError> { + let node_index_path = self.path.as_deref().map(|p| p.path().join("nodes")); self.node_index.entity_index.create_property_index( prop_id, prop_name, @@ -180,6 +377,7 @@ impl GraphIndex { schema.add_u64_field(fields::NODE_ID, INDEXED | FAST | STORED); }, PropertyIndex::new_node_property, + &node_index_path, ) } } @@ -187,7 +385,7 @@ impl GraphIndex { #[cfg(test)] mod graph_index_test { use crate::{ - db::{api::view::SearchableGraphOps, graph::views::property_filter::PropertyFilterOps}, + db::{api::view::SearchableGraphOps, graph::views::filter::model::PropertyFilterOps}, prelude::{AdditionOps, EdgeViewOps, Graph, GraphViewOps, NodeViewOps, PropertyFilter}, }; @@ -230,13 +428,13 @@ mod graph_index_test { assert_eq!(graph.count_nodes(), 3); - let _ = graph.create_index().unwrap(); + let _ = graph.create_index_in_ram().unwrap(); } #[test] fn test_if_adding_nodes_to_existing_graph_index_is_ok() { let graph = Graph::new(); - let _ = graph.create_index().unwrap(); + let _ = graph.create_index_in_ram().unwrap(); let graph = init_nodes_graph(graph); @@ -247,7 +445,7 @@ mod graph_index_test { fn test_if_adding_edges_to_existing_graph_index_is_ok() { let graph = Graph::new(); // Creates graph index - let _ = graph.create_index().unwrap(); + let _ = graph.create_index_in_ram().unwrap(); let graph = init_edges_graph(graph); @@ -258,7 +456,7 @@ mod graph_index_test { fn test_node_const_property_graph_index_is_ok() { let graph = Graph::new(); let graph = init_nodes_graph(graph); - graph.create_index().unwrap(); + graph.create_index_in_ram().unwrap(); graph .node(1) .unwrap() @@ -295,7 +493,7 @@ mod graph_index_test { fn test_edge_const_property_graph_index_is_ok() { let graph = Graph::new(); let graph = init_edges_graph(graph); - graph.create_index().unwrap(); + graph.create_index_in_ram().unwrap(); graph .edge(1, 2) .unwrap() diff --git a/raphtory/src/search/mod.rs b/raphtory/src/search/mod.rs index e25dcb7a9f..26c12f3810 100644 --- a/raphtory/src/search/mod.rs +++ b/raphtory/src/search/mod.rs @@ -1,3 +1,5 @@ +use crate::core::utils::errors::GraphError; +use std::{fs::create_dir_all, path::PathBuf}; use tantivy::{ schema::Schema, tokenizer::{LowerCaser, SimpleTokenizer, TextAnalyzer}, @@ -21,32 +23,58 @@ pub(in crate::search) mod fields { pub const SECONDARY_TIME: &str = "secondary_time"; pub const NODE_ID: &str = "node_id"; pub const NODE_NAME: &str = "node_name"; + pub const NODE_NAME_TOKENIZED: &str = "node_name_tokenized"; pub const NODE_TYPE: &str = "node_type"; + pub const NODE_TYPE_TOKENIZED: &str = "node_type_tokenized"; pub const EDGE_ID: &str = "edge_id"; pub const SOURCE: &str = "src"; + pub const SOURCE_TOKENIZED: &str = "src_tokenized"; pub const DESTINATION: &str = "dst"; + pub const DESTINATION_TOKENIZED: &str = "dst_tokenized"; pub const LAYER_ID: &str = "layer_id"; } pub(crate) const TOKENIZER: &str = "custom_default"; -pub(crate) fn new_index(schema: Schema) -> (Index, IndexReader) { - let index = Index::builder() +pub fn register_default_tokenizers(index: &Index) { + let tokenizer = TextAnalyzer::builder(SimpleTokenizer::default()) + .filter(LowerCaser) + .build(); + index.tokenizers().register(TOKENIZER, tokenizer); +} + +pub(crate) fn new_index( + schema: Schema, + path: &Option, +) -> Result<(Index, IndexReader), GraphError> { + let index_builder = Index::builder() .settings(IndexSettings::default()) - .schema(schema) - .create_in_ram() - .expect("Failed to create index"); + .schema(schema); + + let index = if let Some(path) = path { + create_dir_all(path).map_err(|e| { + GraphError::IOErrorMsg(format!( + "Failed to create index directory {}: {}", + path.display(), + e + )) + })?; + + index_builder.create_in_dir(path).map_err(|e| { + GraphError::IndexErrorMsg(format!("Failed to create index in directory: {}", e)) + })? + } else { + index_builder.create_in_ram().map_err(|e| { + GraphError::IndexErrorMsg(format!("Failed to create in-memory index: {}", e)) + })? + }; let reader = index .reader_builder() .reload_policy(tantivy::ReloadPolicy::Manual) - .try_into() - .unwrap(); + .try_into()?; - let tokenizer = TextAnalyzer::builder(SimpleTokenizer::default()) - .filter(LowerCaser) - .build(); - index.tokenizers().register(TOKENIZER, tokenizer); + register_default_tokenizers(&index); - (index, reader) + Ok((index, reader)) } diff --git a/raphtory/src/search/node_filter_executor.rs b/raphtory/src/search/node_filter_executor.rs index 266393472c..364013bdb7 100644 --- a/raphtory/src/search/node_filter_executor.rs +++ b/raphtory/src/search/node_filter_executor.rs @@ -4,10 +4,17 @@ use crate::{ api::view::StaticGraphViewOps, graph::{ node::NodeView, - views::property_filter::{CompositeNodeFilter, Filter, PropertyRef, Temporal}, + views::filter::{ + internal::InternalNodeFilterOps, + model::{ + node_filter::{CompositeNodeFilter, NodeNameFilter, NodeTypeFilter}, + property_filter::{PropertyRef, Temporal}, + Filter, + }, + }, }, }, - prelude::{NodePropertyFilterOps, NodeViewOps, PropertyFilter, ResetFilter}, + prelude::{GraphViewOps, NodePropertyFilterOps, NodeViewOps, PropertyFilter}, search::{ collectors::{ latest_node_property_filter_collector::LatestNodePropertyFilterCollector, @@ -129,7 +136,6 @@ impl<'a> NodeFilterExecutor<'a> { collector_fn, ), // Fallback to raphtory apis - // Query is none for "is_none" filters because it's cheaper to just ask raphtory None => Self::raph_filter_nodes(graph, filter, limit, offset), } } @@ -317,9 +323,15 @@ impl<'a> NodeFilterExecutor<'a> { limit, offset, )?, - None => { - vec![] - } + None => match filter.field_name.as_str() { + "node_name" => { + Self::raph_filter_nodes(graph, &NodeNameFilter(filter.clone()), limit, offset)? + } + "node_type" => { + Self::raph_filter_nodes(graph, &NodeTypeFilter(filter.clone()), limit, offset)? + } + _ => vec![], + }, }; Ok(results) @@ -339,35 +351,29 @@ impl<'a> NodeFilterExecutor<'a> { CompositeNodeFilter::Node(filter) => { self.filter_node_index(graph, filter, limit, offset) } - CompositeNodeFilter::And(filters) => { - let mut results = None; - - for sub_filter in filters { - let sub_result = self.filter_nodes(graph, sub_filter, limit, offset)?; - - results = Some( - results - .map(|r: Vec<_>| { - r.into_iter() - .filter(|item| sub_result.contains(item)) - .collect::>() // Ensure intersection results stay in a Vec - }) - .unwrap_or(sub_result), - ); - } + CompositeNodeFilter::And(left, right) => { + let left_result = self.filter_nodes(graph, left, limit, offset)?; + let right_result = self.filter_nodes(graph, right, limit, offset)?; + + let left_set: HashSet<_> = left_result.into_iter().collect(); + let intersection = right_result + .into_iter() + .filter(|n| left_set.contains(n)) + .collect::>(); - Ok(results.unwrap_or_default()) + Ok(intersection) } - CompositeNodeFilter::Or(filters) => { - let mut results = HashSet::new(); + CompositeNodeFilter::Or(left, right) => { + let left_result = self.filter_nodes(graph, left, limit, offset)?; + let right_result = self.filter_nodes(graph, right, limit, offset)?; - for sub_filter in filters { - let sub_result = self.filter_nodes(graph, sub_filter, limit, offset)?; - results.extend(sub_result); - } + let mut combined = HashSet::new(); + combined.extend(left_result); + combined.extend(right_result); - Ok(results.into_iter().collect()) + Ok(combined.into_iter().collect()) } + CompositeNodeFilter::Not(_) => Self::raph_filter_nodes(graph, filter, limit, offset), } } @@ -422,18 +428,19 @@ impl<'a> NodeFilterExecutor<'a> { fn raph_filter_nodes( graph: &G, - filter: &PropertyFilter, + filter: &(impl InternalNodeFilterOps + Clone), limit: usize, offset: usize, ) -> Result>, GraphError> { - Ok(graph - .nodes() + let filtered_nodes = graph .filter_nodes(filter.clone())? - .into_iter() - .map(|n| n.reset_filter()) + .nodes() + .iter() + .map(|n| NodeView::new_internal(graph.clone(), n.node)) .skip(offset) .take(limit) - .collect()) + .collect(); + Ok(filtered_nodes) } #[allow(dead_code)] diff --git a/raphtory/src/search/node_index.rs b/raphtory/src/search/node_index.rs index fd2a8b26bb..27414462b9 100644 --- a/raphtory/src/search/node_index.rs +++ b/raphtory/src/search/node_index.rs @@ -16,19 +16,22 @@ use crate::{ prelude::*, search::{ entity_index::EntityIndex, - fields::{NODE_ID, NODE_NAME, NODE_TYPE}, + fields::{NODE_ID, NODE_NAME, NODE_NAME_TOKENIZED, NODE_TYPE, NODE_TYPE_TOKENIZED}, TOKENIZER, }, }; use raphtory_api::core::storage::{arc_str::ArcStr, dict_mapper::MaybeNew}; use rayon::{prelude::ParallelIterator, slice::ParallelSlice}; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + path::{Path, PathBuf}, +}; use tantivy::{ collector::TopDocs, query::AllQuery, schema::{ Field, IndexRecordOption, Schema, SchemaBuilder, TextFieldIndexing, TextOptions, FAST, - INDEXED, STORED, + INDEXED, STORED, STRING, }, Document, IndexWriter, TantivyDocument, }; @@ -38,7 +41,9 @@ pub struct NodeIndex { pub(crate) entity_index: EntityIndex, pub(crate) node_id_field: Field, pub(crate) node_name_field: Field, + pub(crate) node_name_tokenized_field: Field, pub(crate) node_type_field: Field, + pub(crate) node_type_tokenized_field: Field, } impl Debug for NodeIndex { @@ -50,39 +55,94 @@ impl Debug for NodeIndex { } impl NodeIndex { - pub(crate) fn new() -> Self { + fn fetch_fields(schema: &Schema) -> Result<(Field, Field, Field, Field, Field), GraphError> { + let node_id_field = schema + .get_field(NODE_ID) + .map_err(|_| GraphError::IndexErrorMsg("Node ID field missing in schema.".into()))?; + + let node_name_field = schema + .get_field(NODE_NAME) + .map_err(|_| GraphError::IndexErrorMsg("Node name field missing in schema.".into()))?; + + let node_name_tokenized_field = schema.get_field(NODE_NAME_TOKENIZED).map_err(|_| { + GraphError::IndexErrorMsg("Tokenized node name field missing in schema.".into()) + })?; + + let node_type_field = schema + .get_field(NODE_TYPE) + .map_err(|_| GraphError::IndexErrorMsg("Node type field missing in schema.".into()))?; + + let node_type_tokenized_field = schema.get_field(NODE_TYPE_TOKENIZED).map_err(|_| { + GraphError::IndexErrorMsg("Tokenized node type field missing in schema.".into()) + })?; + + Ok(( + node_id_field, + node_name_field, + node_name_tokenized_field, + node_type_field, + node_type_tokenized_field, + )) + } + + pub(crate) fn new(path: &Option) -> Result { let schema = Self::schema_builder().build(); - let node_id_field = schema.get_field(NODE_ID).ok().expect("Node id absent"); - let node_name_field = schema.get_field(NODE_NAME).expect("Node name absent"); - let node_type_field = schema.get_field(NODE_TYPE).expect("Node type absent"); - let entity_index = EntityIndex::new(schema); - Self { + let ( + node_id_field, + node_name_field, + node_name_tokenized_field, + node_type_field, + node_type_tokenized_field, + ) = Self::fetch_fields(&schema)?; + + let entity_index = EntityIndex::new(schema, path)?; + + Ok(Self { entity_index, node_id_field, node_name_field, + node_name_tokenized_field, node_type_field, - } + node_type_tokenized_field, + }) + } + + pub(crate) fn load_from_path(path: &PathBuf) -> Result { + let entity_index = EntityIndex::load_nodes_index_from_path(path)?; + let schema = entity_index.index.schema(); + let ( + node_id_field, + node_name_field, + node_name_tokenized_field, + node_type_field, + node_type_tokenized_field, + ) = Self::fetch_fields(&schema)?; + + Ok(Self { + entity_index, + node_id_field, + node_name_field, + node_name_tokenized_field, + node_type_field, + node_type_tokenized_field, + }) } pub(crate) fn print(&self) -> Result<(), GraphError> { let searcher = self.entity_index.reader.searcher(); let top_docs = searcher.search(&AllQuery, &TopDocs::with_limit(1000))?; - println!("Total node doc count: {}", top_docs.len()); - for (_score, doc_address) in top_docs { let doc: TantivyDocument = searcher.doc(doc_address)?; println!("Node doc: {:?}", doc.to_json(searcher.schema())); } let constant_property_indexes = self.entity_index.const_property_indexes.read(); - for property_index in constant_property_indexes.iter().flatten() { property_index.print()?; } let temporal_property_indexes = self.entity_index.temporal_property_indexes.read(); - for property_index in temporal_property_indexes.iter().flatten() { property_index.print()?; } @@ -93,16 +153,18 @@ impl NodeIndex { fn schema_builder() -> SchemaBuilder { let mut schema_builder: SchemaBuilder = Schema::builder(); schema_builder.add_u64_field(NODE_ID, INDEXED | FAST | STORED); + schema_builder.add_text_field(NODE_NAME, STRING); schema_builder.add_text_field( - NODE_NAME, + NODE_NAME_TOKENIZED, TextOptions::default().set_indexing_options( TextFieldIndexing::default() .set_tokenizer(TOKENIZER) .set_index_option(IndexRecordOption::WithFreqsAndPositions), ), ); + schema_builder.add_text_field(NODE_TYPE, STRING); schema_builder.add_text_field( - NODE_TYPE, + NODE_TYPE_TOKENIZED, TextOptions::default().set_indexing_options( TextFieldIndexing::default() .set_tokenizer(TOKENIZER) @@ -116,6 +178,13 @@ impl NodeIndex { self.entity_index.index.schema().get_field(field_name) } + pub fn get_tokenized_node_field(&self, field_name: &str) -> tantivy::Result { + self.entity_index + .index + .schema() + .get_field(format!("{field_name}_tokenized").as_ref()) + } + fn create_document<'a>( &self, node_id: u64, @@ -124,9 +193,11 @@ impl NodeIndex { ) -> TantivyDocument { let mut document = TantivyDocument::new(); document.add_u64(self.node_id_field, node_id); - document.add_text(self.node_name_field, node_name); + document.add_text(self.node_name_field, node_name.clone()); + document.add_text(self.node_name_tokenized_field, node_name); if let Some(node_type) = node_type { - document.add_text(self.node_type_field, node_type); + document.add_text(self.node_type_field, node_type.clone()); + document.add_text(self.node_type_tokenized_field, node_type); } document } @@ -213,23 +284,41 @@ impl NodeIndex { Ok(()) } - pub(crate) fn index_nodes(graph: &GraphStorage) -> Result { - let node_index = NodeIndex::new(); + pub(crate) fn index_nodes( + graph: &GraphStorage, + path: Option<&Path>, + ) -> Result { + let node_index_path = path.as_deref().map(|p| p.join("nodes")); + let node_index = NodeIndex::new(&node_index_path)?; // Initialize property indexes and get their writers let const_property_keys = graph.node_meta().const_prop_meta().get_keys().into_iter(); + let const_properties_index_path = node_index_path + .as_deref() + .map(|p| p.join("const_properties")); let mut const_writers = node_index .entity_index - .initialize_node_const_property_indexes(graph, const_property_keys)?; + .initialize_node_const_property_indexes( + graph, + const_property_keys, + &const_properties_index_path, + )?; let temporal_property_keys = graph .node_meta() .temporal_prop_meta() .get_keys() .into_iter(); + let temporal_properties_index_path = node_index_path + .as_deref() + .map(|p| p.join("temporal_properties")); let mut temporal_writers = node_index .entity_index - .initialize_node_temporal_property_indexes(graph, temporal_property_keys)?; + .initialize_node_temporal_property_indexes( + graph, + temporal_property_keys, + &temporal_properties_index_path, + )?; // Index nodes in parallel let mut writer = node_index.entity_index.index.writer(100_000_000)?; diff --git a/raphtory/src/search/property_index.rs b/raphtory/src/search/property_index.rs index d3c37b646f..0372adea8c 100644 --- a/raphtory/src/search/property_index.rs +++ b/raphtory/src/search/property_index.rs @@ -4,13 +4,13 @@ use crate::{ search::{fields, new_index, TOKENIZER}, }; use raphtory_api::core::{storage::timeindex::TimeIndexEntry, PropType}; -use std::sync::Arc; +use std::{fs, path::PathBuf, sync::Arc}; use tantivy::{ collector::TopDocs, query::AllQuery, schema::{ Field, IndexRecordOption, Schema, SchemaBuilder, TextFieldIndexing, TextOptions, Type, - FAST, INDEXED, TEXT, + FAST, INDEXED, STRING, TEXT, }, Document, Index, IndexReader, TantivyDocument, }; @@ -26,54 +26,129 @@ pub struct PropertyIndex { } impl PropertyIndex { - fn new_property(schema: Schema, is_edge: bool) -> Self { - let time_field = schema.get_field(fields::TIME).ok(); - let secondary_time_field = schema.get_field(fields::SECONDARY_TIME).ok(); + fn fetch_fields( + schema: &Schema, + is_edge: bool, + ) -> Result<(Option, Option, Option, Field), GraphError> { + let time_field = schema + .get_field(fields::TIME) + .map_err(|_| GraphError::IndexErrorMsg("Missing required field: TIME".into())) + .ok(); + + let secondary_time_field = schema + .get_field(fields::SECONDARY_TIME) + .map_err(|_| GraphError::IndexErrorMsg("Missing required field: SECONDARY_TIME".into())) + .ok(); + + let layer_field = if is_edge { + Some(schema.get_field(fields::LAYER_ID).map_err(|_| { + GraphError::IndexErrorMsg("Missing required field: LAYER_ID".into()) + })?) + } else { + None + }; + let entity_id_field = schema .get_field(if is_edge { fields::EDGE_ID } else { fields::NODE_ID }) - .ok() - .expect(if is_edge { - "Need edge id" - } else { - "Need node id" - }); + .map_err(|_| { + GraphError::IndexErrorMsg(format!( + "Missing required field: {}", + if is_edge { + fields::EDGE_ID + } else { + fields::NODE_ID + } + )) + })?; - let layer_field = if is_edge { - schema.get_field(fields::LAYER_ID).ok() - } else { - None - }; + Ok(( + time_field, + secondary_time_field, + layer_field, + entity_id_field, + )) + } + + fn new_property( + schema: Schema, + is_edge: bool, + path: &Option, + ) -> Result { + let (time_field, secondary_time_field, layer_field, entity_id_field) = + Self::fetch_fields(&schema, is_edge)?; - let (index, reader) = new_index(schema); + let (index, reader) = new_index(schema, path)?; - Self { + Ok(Self { index: Arc::new(index), reader, time_field, secondary_time_field, layer_field, entity_id_field, - } + }) + } + + pub(crate) fn new_node_property( + schema: Schema, + path: &Option, + ) -> Result { + Self::new_property(schema, false, path) } - pub(crate) fn new_node_property(schema: Schema) -> Self { - Self::new_property(schema, false) + pub(crate) fn new_edge_property( + schema: Schema, + path: &Option, + ) -> Result { + Self::new_property(schema, true, path) } - pub(crate) fn new_edge_property(schema: Schema) -> Self { - Self::new_property(schema, true) + fn load_from_path(path: &PathBuf, is_edge: bool) -> Result { + let index = Index::open_in_dir(path)?; + let reader = index + .reader_builder() + .reload_policy(tantivy::ReloadPolicy::Manual) + .try_into()?; + let schema = index.schema(); + let (time_field, secondary_time_field, layer_field, entity_id_field) = + Self::fetch_fields(&schema, is_edge)?; + + Ok(Self { + index: Arc::new(index), + reader, + time_field, + secondary_time_field, + layer_field, + entity_id_field, + }) + } + + pub(crate) fn load_all(path: &PathBuf, is_edge: bool) -> Result>, GraphError> { + if !path.exists() { + return Ok(vec![]); + } + + let mut result = vec![]; + for entry in fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + let prop_index = Self::load_from_path(&path, is_edge)?; + result.push(Some(prop_index)); + } + } + + Ok(result) } pub(crate) fn print(&self) -> Result<(), GraphError> { let searcher = self.reader.searcher(); let top_docs = searcher.search(&AllQuery, &TopDocs::with_limit(100))?; - println!("Total property doc count: {}", top_docs.len()); - for (_score, doc_address) in top_docs { let doc: TantivyDocument = searcher.doc(doc_address)?; println!("Property doc: {:?}", doc.to_json(searcher.schema())); @@ -87,13 +162,16 @@ impl PropertyIndex { match prop_type { PropType::Str => { + schema_builder.add_text_field(prop_name, STRING); schema_builder.add_text_field( - prop_name, - TextOptions::default().set_indexing_options( - TextFieldIndexing::default() - .set_tokenizer(TOKENIZER) - .set_index_option(IndexRecordOption::WithFreqsAndPositions), - ), + format!("{prop_name}_tokenized").as_ref(), + TextOptions::default() + .set_indexing_options( + TextFieldIndexing::default() + .set_tokenizer(TOKENIZER) + .set_index_option(IndexRecordOption::WithFreqsAndPositions), + ) + .set_stored(), ); } PropType::DTime => { @@ -135,6 +213,12 @@ impl PropertyIndex { self.index.schema().get_field(prop_name) } + pub fn get_tokenized_prop_field(&self, prop_name: &str) -> tantivy::Result { + self.index + .schema() + .get_field(format!("{prop_name}_tokenized").as_ref()) + } + pub fn get_prop_field_type(&self, prop_name: &str) -> tantivy::Result { Ok(self .index @@ -144,9 +228,12 @@ impl PropertyIndex { .value_type()) } - fn add_property_value(document: &mut TantivyDocument, field: Field, prop_value: &Prop) { + fn add_property_value_to_doc(document: &mut TantivyDocument, field: Field, prop_value: &Prop) { match prop_value.clone() { - Prop::Str(v) => document.add_text(field, v), + Prop::Str(v) => { + document.add_text(field, v.clone()); + document.add_text(Field::from_field_id(1), v); + } Prop::NDTime(v) => { if let Some(time) = v.and_utc().timestamp_nanos_opt() { document.add_date(field, tantivy::DateTime::from_timestamp_nanos(time)); @@ -188,9 +275,7 @@ impl PropertyIndex { document.add_u64(field_layer_id, layer_id as u64); } - Self::add_property_value(&mut document, field_property, prop_value); - - // println!("Added prop doc: {}", &document.to_json(&schema)); + Self::add_property_value_to_doc(&mut document, field_property, prop_value); Ok(document) } diff --git a/raphtory/src/search/query_builder.rs b/raphtory/src/search/query_builder.rs index 68596b87d7..51c2405254 100644 --- a/raphtory/src/search/query_builder.rs +++ b/raphtory/src/search/query_builder.rs @@ -2,7 +2,10 @@ use crate::{ core::{utils::errors::GraphError, Prop}, db::{ api::view::StaticGraphViewOps, - graph::views::property_filter::{Filter, FilterOperator, FilterValue, PropertyFilterValue}, + graph::views::filter::model::{ + filter_operator::FilterOperator, property_filter::PropertyFilterValue, Filter, + FilterValue, + }, }, prelude::PropertyFilter, search::{ @@ -11,20 +14,16 @@ use crate::{ }, }; use itertools::Itertools; -use std::{ - collections::{Bound, HashSet}, - sync::Arc, - vec, -}; +use std::{collections::Bound, ops::Deref, sync::Arc, vec}; use tantivy::{ query::{ - AllQuery, BooleanQuery, EmptyQuery, FuzzyTermQuery, Occur, + AllQuery, BooleanQuery, EmptyQuery, Occur, Occur::{Must, MustNot, Should}, - PhraseQuery, Query, RangeQuery, TermQuery, + Query, RangeQuery, TermQuery, }, - schema::{Field, FieldType, IndexRecordOption, Schema, Type}, + schema::{Field, FieldType, IndexRecordOption, Type}, tokenizer::TokenizerManager, - Index, Term, + Term, }; #[derive(Clone, Copy)] @@ -44,40 +43,73 @@ impl<'a> QueryBuilder<'a> { ) -> Result>, GraphError> { let prop_name = filter.prop_ref.name(); let prop_value = &filter.prop_value; - let prop_field = property_index.get_prop_field(prop_name)?; let prop_field_type = property_index.get_prop_field_type(prop_name)?; - let query: Option> = match prop_value { - PropertyFilterValue::Single(prop_value) => { - let terms = create_property_tantivy_terms(&property_index, prop_field, prop_value)?; - match &filter.operator { - FilterOperator::Eq => create_eq_query(terms), - FilterOperator::Ne => create_ne_query(terms), - FilterOperator::Lt => { - create_lt_query(prop_name.to_string(), prop_field_type, terms) - } - FilterOperator::Le => { - create_le_query(prop_name.to_string(), prop_field_type, terms) - } - FilterOperator::Gt => { - create_gt_query(prop_name.to_string(), prop_field_type, terms) - } - FilterOperator::Ge => { - create_ge_query(prop_name.to_string(), prop_field_type, terms) - } - FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - } => create_fuzzy_search_query(terms, levenshtein_distance, prefix_match), - _ => unreachable!(), + PropertyFilterValue::Single(prop_value) => match &filter.operator { + FilterOperator::Eq => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_eq_query(term) } - } + FilterOperator::Ne => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_ne_query(term) + } + FilterOperator::Lt => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_lt_query(prop_name.to_string(), prop_field_type, term) + } + FilterOperator::Le => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_le_query(prop_name.to_string(), prop_field_type, term) + } + FilterOperator::Gt => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_gt_query(prop_name.to_string(), prop_field_type, term) + } + FilterOperator::Ge => { + let term = + create_property_exact_tantivy_term(property_index, prop_name, prop_value)?; + create_ge_query(prop_name.to_string(), prop_field_type, term) + } + FilterOperator::Contains => { + let terms = create_property_tokenized_tantivy_terms( + property_index, + prop_name, + prop_value, + )?; + create_contains_query(terms) + } + FilterOperator::NotContains => { + let terms = create_property_tokenized_tantivy_terms( + property_index, + prop_name, + prop_value, + )?; + create_contains_not_query(terms) + } + FilterOperator::FuzzySearch { + levenshtein_distance: _, + prefix_match: _, + } => None, + _ => unreachable!(), + }, PropertyFilterValue::Set(prop_values) => { - let sub_queries = - create_property_sub_queries(&property_index, prop_field, prop_values); + let terms: Result, GraphError> = prop_values + .deref() + .into_iter() + .map(|value| { + create_property_exact_tantivy_term(property_index, prop_name, &value) + }) + .collect(); + let terms = terms?; match &filter.operator { - FilterOperator::In => create_in_query(sub_queries), - FilterOperator::NotIn => create_not_in_query(sub_queries), + FilterOperator::In => create_in_query(terms), + FilterOperator::NotIn => create_not_in_query(terms), _ => unreachable!(), } } @@ -91,55 +123,56 @@ impl<'a> QueryBuilder<'a> { Ok(query) } - fn build_query_generic( + pub(crate) fn build_node_query( &self, - index: &Index, - node_field: Field, - filter_value: &FilterValue, - operator: &FilterOperator, - ) -> Result>, GraphError> { - let schema = &index.schema(); - let tokenizer_manager = index.tokenizers(); + filter: &Filter, + ) -> Result<(Arc, Option>), GraphError> { + let node_index = &self.index.node_index; + let field_name = &filter.field_name; + let filter_value = &filter.field_value; + let operator = &filter.operator; + let query = match filter_value { - FilterValue::Single(node_value) => { - let terms = - create_tantivy_terms(schema, tokenizer_manager, node_field, node_value)?; - match operator { - FilterOperator::Eq => create_eq_query(terms), - FilterOperator::Ne => create_ne_query(terms), - FilterOperator::FuzzySearch { - levenshtein_distance, - prefix_match, - } => create_fuzzy_search_query(terms, levenshtein_distance, prefix_match), - _ => unreachable!(), + FilterValue::Single(node_value) => match operator { + FilterOperator::Eq => { + let term = create_node_exact_tantivy_term(node_index, field_name, node_value)?; + create_eq_query(term) } - } + FilterOperator::Ne => { + let term = create_node_exact_tantivy_term(node_index, field_name, node_value)?; + create_ne_query(term) + } + FilterOperator::Contains => { + let terms = + create_node_tokenized_tantivy_terms(node_index, field_name, node_value)?; + create_contains_query(terms) + } + FilterOperator::NotContains => { + let terms = + create_node_tokenized_tantivy_terms(node_index, field_name, node_value)?; + create_contains_not_query(terms) + } + FilterOperator::FuzzySearch { + levenshtein_distance: _, + prefix_match: _, + } => None, + _ => unreachable!(), + }, FilterValue::Set(node_values) => { - let sub_queries = - create_sub_queries(schema, tokenizer_manager, node_field, node_values); + let terms: Result, GraphError> = node_values + .deref() + .into_iter() + .map(|value| create_node_exact_tantivy_term(node_index, field_name, &value)) + .collect(); + let terms = terms?; match operator { - FilterOperator::In => create_in_query(sub_queries), - FilterOperator::NotIn => create_not_in_query(sub_queries), + FilterOperator::In => create_in_query(terms), + FilterOperator::NotIn => create_not_in_query(terms), _ => unreachable!(), } } }; - Ok(query) - } - - pub(crate) fn build_node_query( - &self, - filter: &Filter, - ) -> Result<(Arc, Option>), GraphError> { - let node_index = &self.index.node_index; - let index = &node_index.entity_index.index; - let field_name = &filter.field_name; - let field = node_index.get_node_field(field_name)?; - let filter_value = &filter.field_value; - let operator = &filter.operator; - - let query = self.build_query_generic(index, field, filter_value, operator)?; Ok((Arc::from(node_index.clone()), query)) } @@ -148,13 +181,51 @@ impl<'a> QueryBuilder<'a> { filter: &Filter, ) -> Result<(Arc, Option>), GraphError> { let edge_index = &self.index.edge_index; - let index = &edge_index.entity_index.index; let field_name = &filter.field_name; - let field = edge_index.get_edge_field(field_name)?; let filter_value = &filter.field_value; let operator = &filter.operator; - let query = self.build_query_generic(index, field, filter_value, operator)?; + let query = match filter_value { + FilterValue::Single(node_value) => match operator { + FilterOperator::Eq => { + let term = create_edge_exact_tantivy_term(edge_index, field_name, node_value)?; + create_eq_query(term) + } + FilterOperator::Ne => { + let term = create_edge_exact_tantivy_term(edge_index, field_name, node_value)?; + create_ne_query(term) + } + FilterOperator::Contains => { + let terms = + create_edge_tokenized_tantivy_terms(edge_index, field_name, node_value)?; + create_contains_query(terms) + } + FilterOperator::NotContains => { + let terms = + create_edge_tokenized_tantivy_terms(edge_index, field_name, node_value)?; + create_contains_not_query(terms) + } + FilterOperator::FuzzySearch { + levenshtein_distance: _, + prefix_match: _, + } => None, + _ => unreachable!(), + }, + FilterValue::Set(edge_values) => { + let terms: Result, GraphError> = edge_values + .deref() + .into_iter() + .map(|value| create_edge_exact_tantivy_term(edge_index, field_name, &value)) + .collect(); + let terms = terms?; + match operator { + FilterOperator::In => create_in_query(terms), + FilterOperator::NotIn => create_not_in_query(terms), + _ => unreachable!(), + } + } + }; + Ok((Arc::from(edge_index.clone()), query)) } } @@ -190,13 +261,31 @@ pub fn get_str_field_tokens( Ok(tokens) } -fn create_property_tantivy_terms( +fn create_property_exact_tantivy_term( + property_index: &Arc, + prop_name: &str, + prop_value: &Prop, +) -> Result { + let prop_field = property_index.get_prop_field(prop_name)?; + match prop_value { + Prop::Str(value) => Ok(Term::from_field_text(prop_field, value.as_ref())), + Prop::I32(value) => Ok(Term::from_field_i64(prop_field, *value as i64)), + Prop::I64(value) => Ok(Term::from_field_i64(prop_field, *value)), + Prop::U64(value) => Ok(Term::from_field_u64(prop_field, *value)), + Prop::F64(value) => Ok(Term::from_field_f64(prop_field, *value)), + Prop::Bool(value) => Ok(Term::from_field_bool(prop_field, *value)), + v => Err(GraphError::UnsupportedValue(v.to_string())), + } +} + +fn create_property_tokenized_tantivy_terms( property_index: &PropertyIndex, - prop_field: Field, + prop_name: &str, prop_value: &Prop, ) -> Result, GraphError> { match prop_value { Prop::Str(value) => { + let prop_field = property_index.get_tokenized_prop_field(prop_name)?; let schema = property_index.index.schema(); let field_entry = schema.get_field_entry(prop_field.clone()); let field_type = field_entry.field_type(); @@ -204,68 +293,88 @@ fn create_property_tantivy_terms( get_str_field_tokens(property_index.index.tokenizers(), field_type, value)?; create_terms_from_tokens(prop_field, tokens) } - Prop::I32(value) => Ok(vec![Term::from_field_i64(prop_field, *value as i64)]), - Prop::I64(value) => Ok(vec![Term::from_field_i64(prop_field, *value)]), - Prop::U64(value) => Ok(vec![Term::from_field_u64(prop_field, *value)]), - Prop::F64(value) => Ok(vec![Term::from_field_f64(prop_field, *value)]), - Prop::Bool(value) => Ok(vec![Term::from_field_bool(prop_field, *value)]), + Prop::I32(value) => { + let prop_field = property_index.get_prop_field(prop_name)?; + Ok(vec![Term::from_field_i64(prop_field, *value as i64)]) + } + Prop::I64(value) => { + let prop_field = property_index.get_prop_field(prop_name)?; + Ok(vec![Term::from_field_i64(prop_field, *value)]) + } + Prop::U64(value) => { + let prop_field = property_index.get_prop_field(prop_name)?; + Ok(vec![Term::from_field_u64(prop_field, *value)]) + } + Prop::F64(value) => { + let prop_field = property_index.get_prop_field(prop_name)?; + Ok(vec![Term::from_field_f64(prop_field, *value)]) + } + Prop::Bool(value) => { + let prop_field = property_index.get_prop_field(prop_name)?; + Ok(vec![Term::from_field_bool(prop_field, *value)]) + } v => Err(GraphError::UnsupportedValue(v.to_string())), } } -fn create_sub_queries_generic( - field_values: &HashSet, - create_terms_fn: F, -) -> Vec<(Occur, Box)> -where - F: Fn(&T) -> Result, GraphError>, - T: Eq + std::hash::Hash, -{ - field_values - .iter() - .filter_map(|value| create_terms_fn(value).ok()) - .flat_map(|terms| { - terms.into_iter().map(|term| { - ( - Should, - Box::new(TermQuery::new(term, IndexRecordOption::Basic)) as Box, - ) - }) +fn create_sub_queries(terms: Vec) -> Vec<(Occur, Box)> { + terms + .into_iter() + .map(|term| { + ( + Should, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)) as Box, + ) }) .collect() } -fn create_property_sub_queries( - property_index: &PropertyIndex, - prop_field: Field, - prop_values: &HashSet, -) -> Vec<(Occur, Box)> { - create_sub_queries_generic(prop_values, |value| { - create_property_tantivy_terms(property_index, prop_field, value) - }) +fn create_node_exact_tantivy_term( + node_index: &NodeIndex, + field_name: &str, + field_value: &str, +) -> Result { + let field = node_index.get_node_field(field_name)?; + Ok(Term::from_field_text(field, field_value)) } -fn create_tantivy_terms( - schema: &Schema, - tokenizer_manager: &TokenizerManager, - field: Field, +fn create_node_tokenized_tantivy_terms( + node_index: &NodeIndex, + field_name: &str, field_value: &str, ) -> Result, GraphError> { + let index = &node_index.entity_index.index; + let schema = &index.schema(); + let tokenizer_manager = index.tokenizers(); + let field = node_index.get_tokenized_node_field(field_name)?; let field_entry = schema.get_field_entry(field.clone()); let field_type = field_entry.field_type(); let tokens = get_str_field_tokens(tokenizer_manager, &field_type, field_value)?; create_terms_from_tokens(field, tokens) } -fn create_sub_queries( - schema: &Schema, - tokenizer_manager: &TokenizerManager, - field: Field, - field_values: &HashSet, -) -> Vec<(Occur, Box)> { - create_sub_queries_generic(field_values, |value| { - create_tantivy_terms(schema, tokenizer_manager, field, value) - }) +fn create_edge_exact_tantivy_term( + edge_index: &EdgeIndex, + field_name: &str, + field_value: &str, +) -> Result { + let field = edge_index.get_edge_field(field_name)?; + Ok(Term::from_field_text(field, field_value)) +} + +fn create_edge_tokenized_tantivy_terms( + edge_index: &EdgeIndex, + field_name: &str, + field_value: &str, +) -> Result, GraphError> { + let index = &edge_index.entity_index.index; + let schema = &index.schema(); + let tokenizer_manager = index.tokenizers(); + let field = edge_index.get_tokenized_edge_field(field_name)?; + let field_entry = schema.get_field_entry(field.clone()); + let field_type = field_entry.field_type(); + let tokens = get_str_field_tokens(tokenizer_manager, &field_type, field_value)?; + create_terms_from_tokens(field, tokens) } fn create_terms_from_tokens(field: Field, tokens: Vec) -> Result, GraphError> { @@ -275,120 +384,92 @@ fn create_terms_from_tokens(field: Field, tokens: Vec) -> Result) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - 1 => Some(Box::new(TermQuery::new( - terms[0].clone(), - IndexRecordOption::Basic, - ))), - _ => Some(Box::new(PhraseQuery::new(terms))), - } +fn create_eq_query(term: Term) -> Option> { + Some(Box::new(TermQuery::new(term, IndexRecordOption::Basic))) } -fn create_ne_query(terms: Vec) -> Option> { - if terms.is_empty() { - return Some(Box::new(EmptyQuery)); - } +fn create_ne_query(term: Term) -> Option> { + Some(Box::new(BooleanQuery::new(vec![ + (Should, Box::new(AllQuery)), + ( + MustNot, + Box::new(TermQuery::new(term, IndexRecordOption::Basic)), + ), + ]))) +} - let must_not_queries: Vec<(Occur, Box)> = terms - .into_iter() - .map(|term| { - ( - MustNot, - Box::new(TermQuery::new(term, IndexRecordOption::Basic)) as Box, - ) - }) - .collect(); +fn create_lt_query(prop_name: String, prop_field_type: Type, term: Term) -> Option> { + Some(Box::new(RangeQuery::new_term_bounds( + prop_name, + prop_field_type, + &Bound::Unbounded, + &Bound::Excluded(term), + ))) +} - Some(Box::new(BooleanQuery::new( - vec![(Should, Box::new(AllQuery) as Box)] // Include all documents - .into_iter() - .chain(must_not_queries) // Exclude documents matching any term - .collect(), +fn create_le_query(prop_name: String, prop_field_type: Type, term: Term) -> Option> { + Some(Box::new(RangeQuery::new_term_bounds( + prop_name.to_string(), + prop_field_type, + &Bound::Unbounded, + &Bound::Included(term), ))) } -fn create_lt_query( - prop_name: String, - prop_field_type: Type, - terms: Vec, -) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - _ => Some(Box::new(RangeQuery::new_term_bounds( - prop_name, - prop_field_type, - &Bound::Unbounded, - &Bound::Excluded(terms.get(0).unwrap().clone()), - ))), - } +fn create_gt_query(prop_name: String, prop_field_type: Type, term: Term) -> Option> { + Some(Box::new(RangeQuery::new_term_bounds( + prop_name.to_string(), + prop_field_type, + &Bound::Excluded(term), + &Bound::Unbounded, + ))) } -fn create_le_query( - prop_name: String, - prop_field_type: Type, - terms: Vec, -) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - _ => Some(Box::new(RangeQuery::new_term_bounds( - prop_name.to_string(), - prop_field_type, - &Bound::Unbounded, - &Bound::Included(terms.get(0).unwrap().clone()), - ))), - } +fn create_ge_query(prop_name: String, prop_field_type: Type, term: Term) -> Option> { + Some(Box::new(RangeQuery::new_term_bounds( + prop_name.to_string(), + prop_field_type, + &Bound::Included(term), + &Bound::Unbounded, + ))) } -fn create_gt_query( - prop_name: String, - prop_field_type: Type, - terms: Vec, -) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - _ => Some(Box::new(RangeQuery::new_term_bounds( - prop_name.to_string(), - prop_field_type, - &Bound::Excluded(terms.get(0).unwrap().clone()), - &Bound::Unbounded, - ))), +fn create_in_query(terms: Vec) -> Option> { + if !terms.is_empty() { + let sub_queries = create_sub_queries(terms); + Some(Box::new(BooleanQuery::new(sub_queries))) + } else { + Some(Box::new(EmptyQuery)) } } -fn create_ge_query( - prop_name: String, - prop_field_type: Type, - terms: Vec, -) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - _ => Some(Box::new(RangeQuery::new_term_bounds( - prop_name.to_string(), - prop_field_type, - &Bound::Included(terms.get(0).unwrap().clone()), - &Bound::Unbounded, - ))), +fn create_not_in_query(terms: Vec) -> Option> { + if !terms.is_empty() { + let sub_queries = create_sub_queries(terms); + Some(Box::new(BooleanQuery::new(vec![ + (Must, Box::new(AllQuery)), // Include all documents + ( + MustNot, + Box::new(BooleanQuery::new(sub_queries)), // Exclude matching terms + ), + ]))) + } else { + Some(Box::new(EmptyQuery)) } } -fn create_in_query(sub_queries: Vec<(Occur, Box)>) -> Option> { - if !sub_queries.is_empty() { +fn create_contains_query(terms: Vec) -> Option> { + if !terms.is_empty() { + let sub_queries = create_sub_queries(terms); Some(Box::new(BooleanQuery::new(sub_queries))) } else { Some(Box::new(EmptyQuery)) } } -fn create_not_in_query(sub_queries: Vec<(Occur, Box)>) -> Option> { - if !sub_queries.is_empty() { +fn create_contains_not_query(terms: Vec) -> Option> { + if !terms.is_empty() { + let sub_queries = create_sub_queries(terms); Some(Box::new(BooleanQuery::new(vec![ (Must, Box::new(AllQuery)), // Include all documents ( @@ -400,29 +481,3 @@ fn create_not_in_query(sub_queries: Vec<(Occur, Box)>) -> Option, - levenshtein_distance: &usize, - prefix_match: &bool, -) -> Option> { - match terms.len() { - 0 => Some(Box::new(EmptyQuery)), - 1 => { - if *prefix_match { - Some(Box::new(FuzzyTermQuery::new_prefix( - terms[0].clone(), - (*levenshtein_distance) as u8, - true, - ))) - } else { - Some(Box::new(FuzzyTermQuery::new( - terms[0].clone(), - (*levenshtein_distance) as u8, - true, - ))) - } - } - _ => Some(Box::new(PhraseQuery::new(terms))), // TODO: Refer composite filter fuzzy searching based on strsim::levenshtein - } -} diff --git a/raphtory/src/search/searcher.rs b/raphtory/src/search/searcher.rs index 7f205fd072..3e3526088a 100644 --- a/raphtory/src/search/searcher.rs +++ b/raphtory/src/search/searcher.rs @@ -5,7 +5,7 @@ use crate::{ graph::{ edge::EdgeView, node::NodeView, - views::property_filter::{resolve_as_edge_filter, resolve_as_node_filter, FilterExpr}, + views::filter::model::{AsEdgeFilter, AsNodeFilter}, }, }, search::{ @@ -28,39 +28,45 @@ impl<'a> Searcher<'a> { } } - pub fn search_nodes( + pub fn search_nodes( &self, graph: &G, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, - ) -> Result>, GraphError> { - let filter = resolve_as_node_filter(filter)?; + ) -> Result>, GraphError> + where + G: StaticGraphViewOps, + F: AsNodeFilter, + { + let filter = filter.as_node_filter(); self.node_filter_executor .filter_nodes(graph, &filter, limit, offset) } - pub fn search_edges( + pub fn search_edges( &self, graph: &G, - filter: FilterExpr, + filter: F, limit: usize, offset: usize, - ) -> Result>, GraphError> { - let filter = resolve_as_edge_filter(filter)?; + ) -> Result>, GraphError> + where + G: StaticGraphViewOps, + F: AsEdgeFilter, + { + let filter = filter.as_edge_filter(); self.edge_filter_executor .filter_edges(graph, &filter, limit, offset) } } -// TODO: Fuzzy search tests are non exhaustive because the fuzzy search -// semantics are still undecided. See Query Builder. // TODO: All search tests in graph views (db/graph/views) should include // comparisons to filter apis results. #[cfg(test)] mod search_tests { use super::*; - use crate::{db::graph::views::property_filter::NodeFilter, prelude::*}; + use crate::{db::graph::views::filter::model::NodeFilter, prelude::*}; use raphtory_api::core::utils::logging::global_info_logger; use std::time::SystemTime; use tracing::info; @@ -68,821 +74,17 @@ mod search_tests { #[cfg(test)] mod search_nodes { use crate::{ - core::{IntoProp, Prop}, + core::IntoProp, db::{ api::view::SearchableGraphOps, - graph::views::property_filter::{ - FilterExpr, NodeFilter, NodeFilterOps, PropertyFilterOps, + graph::views::filter::model::{ + AsNodeFilter, NodeFilter, NodeFilterBuilderOps, PropertyFilterOps, }, }, prelude::{AdditionOps, Graph, NodeViewOps, PropertyFilter}, }; - #[cfg(test)] - mod test_nodes_latest_any_semantics { - use crate::{ - core::Prop, - db::{ - api::{ - mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, - view::{SearchableGraphOps, StaticGraphViewOps}, - }, - graph::views::property_filter::{FilterExpr, PropertyFilterOps}, - }, - prelude::{ - AdditionOps, Graph, GraphViewOps, NodeViewOps, PropertyAdditionOps, - PropertyFilter, NO_PROPS, - }, - }; - - fn init_graph< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - let nodes = [ - (6, "N1", vec![("p1", Prop::U64(2u64))]), - (7, "N1", vec![("p1", Prop::U64(1u64))]), - (6, "N2", vec![("p1", Prop::U64(1u64))]), - (7, "N2", vec![("p1", Prop::U64(2u64))]), - (8, "N3", vec![("p1", Prop::U64(1u64))]), - (9, "N4", vec![("p1", Prop::U64(1u64))]), - (5, "N5", vec![("p1", Prop::U64(1u64))]), - (6, "N5", vec![("p1", Prop::U64(2u64))]), - (5, "N6", vec![("p1", Prop::U64(1u64))]), - (6, "N6", vec![("p1", Prop::U64(1u64))]), - (3, "N7", vec![("p1", Prop::U64(1u64))]), - (5, "N7", vec![("p1", Prop::U64(1u64))]), - (3, "N8", vec![("p1", Prop::U64(1u64))]), - (4, "N8", vec![("p1", Prop::U64(2u64))]), - (2, "N9", vec![("p1", Prop::U64(2u64))]), - (2, "N10", vec![("q1", Prop::U64(0u64))]), - (2, "N10", vec![("p1", Prop::U64(3u64))]), - (2, "N11", vec![("p1", Prop::U64(3u64))]), - (2, "N11", vec![("q1", Prop::U64(0u64))]), - (2, "N12", vec![("q1", Prop::U64(0u64))]), - (3, "N12", vec![("p1", Prop::U64(3u64))]), - (2, "N13", vec![("q1", Prop::U64(0u64))]), - (3, "N13", vec![("p1", Prop::U64(3u64))]), - (2, "N14", vec![("q1", Prop::U64(0u64))]), - (2, "N15", vec![]), - ]; - - for (id, label, props) in nodes.iter() { - graph.add_node(*id, label, props.clone(), None).unwrap(); - } - - let constant_properties = [ - ("N1", [("p1", Prop::U64(1u64))]), - ("N4", [("p1", Prop::U64(2u64))]), - ("N9", [("p1", Prop::U64(1u64))]), - ("N10", [("p1", Prop::U64(1u64))]), - ("N11", [("p1", Prop::U64(1u64))]), - ("N12", [("p1", Prop::U64(1u64))]), - ("N13", [("p1", Prop::U64(1u64))]), - ("N14", [("p1", Prop::U64(1u64))]), - ("N15", [("p1", Prop::U64(1u64))]), - ]; - - for (node, props) in constant_properties.iter() { - graph - .node(node) - .unwrap() - .add_constant_properties(props.clone()) - .unwrap(); - } - - graph - } - - fn init_graph_for_secondary_indexes< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - graph - .add_node(1, "N16", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_node(1, "N16", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_node(1, "N17", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(1, "N17", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - } - - #[test] - fn test_temporal_any_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - // g.encode("/tmp/graphs/master").unwrap(); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").temporal().any().eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - - assert_eq!( - results, - vec!["N1", "N16", "N17", "N2", "N3", "N4", "N5", "N6", "N7", "N8"] - ) - } - - #[test] - fn test_temporal_latest_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = - PropertyFilter::property("p1").temporal().latest().eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - - assert_eq!(results, vec!["N1", "N16", "N3", "N4", "N6", "N7"]) - } - - #[test] - fn test_property_latest_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - - assert_eq!( - results, - vec!["N1", "N14", "N15", "N16", "N3", "N4", "N6", "N7"] - ) - } - - #[test] - fn test_temporal_any_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").temporal().any().eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec!["N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8"] - ) - } - - #[test] - fn test_temporal_latest_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1", "N3", "N4", "N6", "N7"]) - } - - #[test] - fn test_constant_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").constant().eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec!["N1", "N10", "N11", "N12", "N13", "N14", "N15", "N9"] - ) - } - - #[test] - fn test_property_constant_semantics() { - // For this graph there won't be any temporal property index for property name "p1". - let graph = Graph::new(); - graph - .add_node(2, "N1", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .node("N1") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))]) - .unwrap(); - - graph.add_node(2, "N2", NO_PROPS, None).unwrap(); - graph - .node("N2") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))]) - .unwrap(); - graph.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = graph - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1", "N2"]) - } - - #[test] - fn test_property_temporal_semantics() { - // For this graph there won't be any constant property index for property name "p1". - let graph = Graph::new(); - graph - .add_node(1, "N1", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .node("N1") - .unwrap() - .add_constant_properties([("p2", Prop::U64(1u64))]) - .unwrap(); - - graph - .add_node(2, "N2", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_node(3, "N2", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_node(2, "N3", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_node(3, "N3", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph.add_node(2, "N4", NO_PROPS, None).unwrap(); - graph.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = graph - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1", "N3"]) - } - - #[test] - fn test_property_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_nodes(filter, 10, 0) - .unwrap() - .into_iter() - .map(|nv| nv.name()) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1", "N14", "N15", "N3", "N4", "N6", "N7"]) - } - } - - #[cfg(test)] - mod test_edges_latest_any_semantics { - use crate::{ - core::Prop, - db::{ - api::{ - mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, - view::{SearchableGraphOps, StaticGraphViewOps}, - }, - graph::views::property_filter::{FilterExpr, PropertyFilterOps}, - }, - prelude::{ - AdditionOps, EdgeViewOps, Graph, GraphViewOps, NodeViewOps, - PropertyAdditionOps, PropertyFilter, NO_PROPS, - }, - }; - - fn init_graph< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - graph - .add_edge(6, "N1", "N2", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_edge(7, "N1", "N2", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .edge("N1", "N2") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(6, "N2", "N3", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(7, "N2", "N3", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(8, "N3", "N4", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(9, "N4", "N5", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .edge("N4", "N5") - .unwrap() - .add_constant_properties([("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(5, "N5", "N6", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(6, "N5", "N6", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(5, "N6", "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(6, "N6", "N7", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(3, "N7", "N8", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(5, "N7", "N8", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(3, "N8", "N9", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(4, "N8", "N9", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - .add_edge(2, "N9", "N10", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .edge("N9", "N10") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(2, "N10", "N11", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .add_edge(2, "N10", "N11", [("p1", Prop::U64(3u64))], None) - .unwrap(); - graph - .edge("N10", "N11") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(2, "N11", "N12", [("p1", Prop::U64(3u64))], None) - .unwrap(); - graph - .add_edge(2, "N11", "N12", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .edge("N11", "N12") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(2, "N12", "N13", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .add_edge(3, "N12", "N13", [("p1", Prop::U64(3u64))], None) - .unwrap(); - graph - .edge("N12", "N13") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(2, "N13", "N14", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .add_edge(3, "N13", "N14", [("p1", Prop::U64(3u64))], None) - .unwrap(); - graph - .edge("N13", "N14") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(2, "N14", "N15", [("q1", Prop::U64(0u64))], None) - .unwrap(); - graph - .edge("N14", "N15") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph.add_edge(2, "N15", "N1", NO_PROPS, None).unwrap(); - graph - .edge("N15", "N1") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - } - - fn init_graph_for_secondary_indexes< - G: StaticGraphViewOps - + AdditionOps - + InternalAdditionOps - + InternalPropertyAdditionOps - + PropertyAdditionOps, - >( - graph: G, - ) -> G { - graph - .add_edge(1, "N16", "N15", [("p1", Prop::U64(2u64))], None) - .unwrap(); - graph - .add_edge(1, "N16", "N15", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - graph - .add_edge(1, "N17", "N16", [("p1", Prop::U64(1u64))], None) - .unwrap(); - graph - .add_edge(1, "N17", "N16", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - graph - } - - #[test] - fn test_secondary_indexes_edges() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").temporal().any().eq(1u64); - g.search_edges(filter, 10, 0).unwrap(); - } - - #[test] - fn test_temporal_any_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").temporal().any().eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - - assert_eq!( - results, - vec![ - "N1->N2", "N16->N15", "N17->N16", "N2->N3", "N3->N4", "N4->N5", "N5->N6", - "N6->N7", "N7->N8", "N8->N9" - ] - ) - } - - #[test] - fn test_temporal_latest_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = - PropertyFilter::property("p1").temporal().latest().eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - - assert_eq!( - results, - vec!["N1->N2", "N16->N15", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ) - } - - #[test] - fn test_property_latest_semantics_for_secondary_indexes() { - let g = Graph::new(); - let g = init_graph(g); - let g = init_graph_for_secondary_indexes(g); - g.create_index().unwrap(); - - let filter: FilterExpr = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - - assert_eq!( - results, - vec![ - "N1->N2", "N14->N15", "N15->N1", "N16->N15", "N3->N4", "N4->N5", "N6->N7", - "N7->N8" - ] - ) - } - - #[test] - fn test_temporal_any_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").temporal().any().eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec![ - "N1->N2", "N2->N3", "N3->N4", "N4->N5", "N5->N6", "N6->N7", "N7->N8", - "N8->N9" - ] - ) - } - - #[test] // Wrong - fn test_temporal_latest_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").temporal().latest().eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec!["N1->N2", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ) - } - - #[test] - fn test_constant_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").constant().eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec![ - "N1->N2", "N10->N11", "N11->N12", "N12->N13", "N13->N14", "N14->N15", - "N15->N1", "N9->N10" - ] - ) - } - - #[test] // Wrong - fn test_property_constant_semantics() { - // For this graph there won't be any temporal property index for property name "p1". - let g = Graph::new(); - g.add_edge(2, "N1", "N2", [("q1", Prop::U64(0u64))], None) - .unwrap(); - g.edge("N1", "N2") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - - g.add_edge(2, "N2", "N3", NO_PROPS, None).unwrap(); - g.edge("N2", "N3") - .unwrap() - .add_constant_properties([("p1", Prop::U64(1u64))], None) - .unwrap(); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1->N2", "N2->N3"]) - } - - #[test] - fn test_property_temporal_semantics() { - // For this graph there won't be any constant property index for property name "p1". - let g = Graph::new(); - g.add_edge(1, "N1", "N2", [("p1", Prop::U64(1u64))], None) - .unwrap(); - g.edge("N1", "N2") - .unwrap() - .add_constant_properties([("p2", Prop::U64(1u64))], None) - .unwrap(); - - g.add_edge(2, "N2", "N3", [("p1", Prop::U64(1u64))], None) - .unwrap(); - g.add_edge(3, "N2", "N3", [("p1", Prop::U64(2u64))], None) - .unwrap(); - - g.add_edge(2, "N3", "N4", [("p1", Prop::U64(2u64))], None) - .unwrap(); - g.add_edge(3, "N3", "N4", [("p1", Prop::U64(1u64))], None) - .unwrap(); - - g.add_edge(2, "N4", "N5", NO_PROPS, None).unwrap(); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!(results, vec!["N1->N2", "N3->N4"]) - } - - #[test] - fn test_property_semantics() { - let g = Graph::new(); - let g = init_graph(g); - g.create_index().unwrap(); - - let filter = PropertyFilter::property("p1").eq(1u64); - let mut results = g - .search_edges(filter, 10, 0) - .unwrap() - .into_iter() - .map(|ev| format!("{}->{}", ev.src().name(), ev.dst().name())) - .collect::>(); - results.sort(); - assert_eq!( - results, - vec!["N1->N2", "N14->N15", "N15->N1", "N3->N4", "N4->N5", "N6->N7", "N7->N8"] - ) - } - } - - fn search_nodes_by_composite_filter(filter: FilterExpr) -> Vec { - let graph = Graph::new(); - graph - .add_node( - 1, - 1, - [ - ("p1", "shivam_kapoor".into_prop()), - ("p9", 5u64.into_prop()), - ], - Some("fire_nation"), - ) - .unwrap(); - graph - .add_node( - 2, - 2, - [("p1", "prop12".into_prop()), ("p2", 2u64.into_prop())], - Some("air_nomads"), - ) - .unwrap(); - graph - .add_node( - 3, - 1, - [ - ("p1", "shivam_kapoor".into_prop()), - ("p9", 5u64.into_prop()), - ], - Some("fire_nation"), - ) - .unwrap(); - graph - .add_node(3, 3, [("p2", 6u64), ("p3", 1u64)], Some("fire_nation")) - .unwrap(); - graph - .add_node( - 4, - 1, - [ - ("p1", "shivam_kapoor".into_prop()), - ("p9", 5u64.into_prop()), - ], - Some("fire_nation"), - ) - .unwrap(); - graph.add_node(3, 4, [("p4", "pometry")], None).unwrap(); - graph.add_node(4, 4, [("p5", 12u64)], None).unwrap(); - - graph.create_index().unwrap(); - - let mut results = graph - .search_nodes(filter, 10, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|v| v.name()) - .collect::>(); - results.sort(); - - results - } - - fn fuzzy_search_nodes_by_composite_filter(filter: FilterExpr) -> Vec { + fn fuzzy_search_nodes(filter: impl AsNodeFilter) -> Vec { let graph = Graph::new(); graph .add_node( @@ -901,7 +103,7 @@ mod search_tests { ) .unwrap(); - graph.create_index().unwrap(); + graph.create_index_in_ram().unwrap(); let mut results = graph .search_nodes(filter, 10, 0) @@ -913,288 +115,61 @@ mod search_tests { results } - #[test] - fn test_search_nodes_by_composite_filter() { - let filter = PropertyFilter::property("p2") - .eq(2u64) - .and(PropertyFilter::property("p1").eq(3u64)); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, Vec::::new()); - - let filter = PropertyFilter::property("p2") - .eq(2u64) - .or(PropertyFilter::property("p1").eq("shivam")); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "2"]); - - let filter = PropertyFilter::property("p1") - .eq("pometry") - .or(PropertyFilter::property("p2") - .eq(6u64) - .and(PropertyFilter::property("p3").eq(1u64))); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["3"]); - - let filter = NodeFilter::node_type() - .eq("fire_nation") - .and(PropertyFilter::property("p1").eq("prop1")); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, Vec::::new()); - } - - #[test] - fn search_nodes_for_node_name_eq() { - let filter = NodeFilter::node_name().eq("3"); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["3"]); - } - - #[test] - fn search_nodes_for_node_name_ne() { - let filter = NodeFilter::node_name().ne("2"); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "3", "4"]); - } - - #[test] - fn search_nodes_for_node_name_in() { - let filter = NodeFilter::node_name().includes(vec!["1".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1"]); - - let filter = NodeFilter::node_name().includes(vec!["2".into(), "3".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_node_name_not_in() { - let filter = NodeFilter::node_name().excludes(vec!["1".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3", "4"]); - } - - #[test] - fn search_nodes_for_node_type_eq() { - let filter = NodeFilter::node_type().eq("fire_nation"); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "3"]); - } - - #[test] - fn search_nodes_for_node_type_ne() { - let filter = NodeFilter::node_type().ne("fire_nation"); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "4"]); - } - - #[test] - fn search_nodes_for_node_type_in() { - let filter = NodeFilter::node_type().includes(vec!["fire_nation".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "3"]); - - let filter = - NodeFilter::node_type().includes(vec!["fire_nation".into(), "air_nomads".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "2", "3"]); - } - - #[test] - fn search_nodes_for_node_type_not_in() { - let filter = NodeFilter::node_type().excludes(vec!["fire_nation".into()]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "4"]); - } - - #[test] - fn search_nodes_for_property_eq() { - let filter = PropertyFilter::property("p2").eq(2u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2"]); - } - - #[test] - fn search_nodes_for_property_ne() { - let filter = PropertyFilter::property("p2").ne(2u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["3"]); - } - - #[test] - fn search_nodes_for_property_lt() { - let filter = PropertyFilter::property("p2").lt(10u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_property_le() { - let filter = PropertyFilter::property("p2").le(6u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_property_gt() { - let filter = PropertyFilter::property("p2").gt(2u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["3"]); - } - - #[test] - fn search_nodes_for_property_ge() { - let filter = PropertyFilter::property("p2").ge(2u64); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_property_in() { - let filter = PropertyFilter::property("p2").includes(vec![Prop::U64(6)]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["3"]); - - let filter = PropertyFilter::property("p2").includes(vec![Prop::U64(2), Prop::U64(6)]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_property_not_in() { - let filter = PropertyFilter::property("p2").excludes(vec![Prop::U64(6)]); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2"]); - } - - #[test] - fn search_nodes_for_property_is_some() { - let filter = PropertyFilter::property("p2").is_some(); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["2", "3"]); - } - - #[test] - fn search_nodes_for_property_is_none() { - let filter = PropertyFilter::property("p2").is_none(); - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["1", "4"]); - } - - #[test] - fn test_search_nodes_by_props_added_at_different_times() { - let filter = PropertyFilter::property("p4") - .eq("pometry") - .and(PropertyFilter::property("p5").eq(12u64)); - - let results = search_nodes_by_composite_filter(filter); - assert_eq!(results, vec!["4"]); - } - #[test] fn test_fuzzy_search() { - let filter = NodeFilter::node_name().fuzzy_search("shivam_kapoor", 2, false); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let filter = NodeFilter::name().fuzzy_search("shivam_kapoor", 2, false); + let results = fuzzy_search_nodes(filter); assert_eq!(results, vec!["shivam_kapoor"]); - let filter = NodeFilter::node_name().fuzzy_search("pomet", 2, false); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let filter = NodeFilter::name().fuzzy_search("pomet", 2, false); + let results = fuzzy_search_nodes(filter); assert_eq!(results, vec!["pometry"]); } #[test] fn test_fuzzy_search_prefix_match() { - let filter = NodeFilter::node_name().fuzzy_search("pome", 2, false); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let filter = NodeFilter::name().fuzzy_search("pome", 2, false); + let results = fuzzy_search_nodes(filter); assert_eq!(results, Vec::::new()); - let filter = NodeFilter::node_name().fuzzy_search("pome", 2, true); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let filter = NodeFilter::name().fuzzy_search("pome", 2, true); + let results = fuzzy_search_nodes(filter); assert_eq!(results, vec!["pometry"]); } #[test] fn test_fuzzy_search_property() { let filter = PropertyFilter::property("p1").fuzzy_search("tano", 2, false); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let results = fuzzy_search_nodes(filter); assert_eq!(results, vec!["pometry"]); } #[test] fn test_fuzzy_search_property_prefix_match() { let filter = PropertyFilter::property("p1").fuzzy_search("char", 2, false); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let results = fuzzy_search_nodes(filter); assert_eq!(results, Vec::::new()); let filter = PropertyFilter::property("p1").fuzzy_search("char", 2, true); - let results = fuzzy_search_nodes_by_composite_filter(filter); + let results = fuzzy_search_nodes(filter); assert_eq!(results, vec!["shivam_kapoor"]); } - - // More tests for windowed graph and other graph views can be found in their respective files - // under src/db/graph/views } #[cfg(test)] mod search_edges { use crate::{ - core::{IntoProp, Prop}, + core::IntoProp, db::{ api::view::SearchableGraphOps, - graph::views::property_filter::{ - EdgeFilter, EdgeFilterOps, FilterExpr, PropertyFilterOps, + graph::views::filter::model::{ + AsEdgeFilter, EdgeFilter, EdgeFilterOps, PropertyFilterOps, }, }, prelude::{AdditionOps, EdgeViewOps, Graph, NodeViewOps, PropertyFilter}, }; - fn search_edges(filter: FilterExpr) -> Vec<(String, String)> { - let graph = Graph::new(); - - graph - .add_edge(1, 1, 2, [("p1", "shivam_kapoor")], Some("fire_nation")) - .unwrap(); - graph - .add_edge( - 2, - 1, - 2, - [ - ("p1", "shivam_kapoor".into_prop()), - ("p2", 4u64.into_prop()), - ], - Some("fire_nation"), - ) - .unwrap(); - graph - .add_edge( - 2, - 2, - 3, - [("p1", "prop12".into_prop()), ("p2", 2u64.into_prop())], - Some("air_nomads"), - ) - .unwrap(); - graph - .add_edge(3, 3, 1, [("p2", 6u64), ("p3", 1u64)], Some("fire_nation")) - .unwrap(); - graph - .add_edge(3, 2, 1, [("p2", 6u64), ("p3", 1u64)], None) - .unwrap(); - - graph.create_index().unwrap(); - let mut results = graph - .search_edges(filter, 5, 0) - .expect("Failed to search for nodes") - .into_iter() - .map(|e| (e.src().name(), e.dst().name())) - .collect::>(); - results.sort(); - - results - } - - fn fuzzy_search_edges_by_composite_filter(filter: FilterExpr) -> Vec<(String, String)> { + fn fuzzy_search_edges(filter: impl AsEdgeFilter) -> Vec<(String, String)> { let graph = Graph::new(); graph .add_edge( @@ -1224,7 +199,7 @@ mod search_tests { ) .unwrap(); - graph.create_index().unwrap(); + graph.create_index_in_ram().unwrap(); let mut results = graph .search_edges(filter, 5, 0) @@ -1237,317 +212,43 @@ mod search_tests { results } - #[test] - fn test_search_edges_by_composite_filter() { - let filter = PropertyFilter::property("p2") - .eq(2u64) - .and(PropertyFilter::property("p1").eq(3u64)); - let results = search_edges(filter); - assert_eq!(results, Vec::<(String, String)>::new()); - - let filter = PropertyFilter::property("p2") - .eq(2u64) - .or(PropertyFilter::property("p1").eq("shivam")); - let results = search_edges(filter); - assert_eq!( - results, - vec![("1".into(), "2".into()), ("2".into(), "3".into())] - ); - - let filter = PropertyFilter::property("p1") - .eq("pometry") - .or(PropertyFilter::property("p2") - .eq(6u64) - .and(PropertyFilter::property("p3").eq(1u64))); - let results = search_edges(filter); - assert_eq!( - results, - vec![("2".into(), "1".into()), ("3".into(), "1".into())] - ); - - let filter = EdgeFilter::src() - .eq("13") - .and(PropertyFilter::property("p1").eq("prop1")); - let results = search_edges(filter); - assert_eq!(results, Vec::<(String, String)>::new()); - } - - #[test] - fn search_edges_for_dst_eq() { - let filter = EdgeFilter::dst().eq("2"); - let results = search_edges(filter); - assert_eq!(results, vec![("1".into(), "2".into())]); - } - - #[test] - fn search_edges_for_dst_ne() { - let filter = EdgeFilter::dst().ne("2"); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_dst_in() { - let filter = EdgeFilter::dst().includes(vec!["2".into()]); - let results = search_edges(filter); - assert_eq!(results, vec![("1".into(), "2".into())]); - - let filter = EdgeFilter::dst().includes(vec!["2".into(), "3".into()]); - let results = search_edges(filter); - assert_eq!( - results, - vec![("1".into(), "2".into()), ("2".into(), "3".into())] - ); - } - - #[test] - fn search_edges_for_dst_not_in() { - let filter = EdgeFilter::dst().excludes(vec!["1".into()]); - let results = search_edges(filter); - assert_eq!( - results, - vec![("1".into(), "2".into()), ("2".into(), "3".into())] - ); - } - - #[test] - fn search_edges_for_src_eq() { - let filter = EdgeFilter::src().eq("3"); - let results = search_edges(filter); - assert_eq!(results, vec![("3".into(), "1".into())]); - } - - #[test] - fn search_edges_for_src_ne() { - let filter = EdgeFilter::src().ne("1"); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_src_in() { - let filter = EdgeFilter::src().includes(vec!["1".into()]); - let results = search_edges(filter); - assert_eq!(results, vec![("1".into(), "2".into())]); - - let filter = EdgeFilter::src().includes(vec!["1".into(), "2".into()]); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("2".into(), "3".into()) - ] - ); - } - - #[test] - fn search_edges_for_src_not_in() { - let filter = EdgeFilter::src().excludes(vec!["1".into()]); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_eq() { - let filter = PropertyFilter::property("p2").eq(2u64); - let results = search_edges(filter); - assert_eq!(results, vec![("2".into(), "3".into())]); - } - - #[test] - fn search_edges_for_property_ne() { - let filter = PropertyFilter::property("p2").ne(2u64); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_lt() { - let filter = PropertyFilter::property("p2").lt(10u64); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_le() { - let filter = PropertyFilter::property("p2").le(6u64); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_gt() { - let filter = PropertyFilter::property("p2").gt(2u64); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_ge() { - let filter = PropertyFilter::property("p2").ge(2u64); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_in() { - let filter = PropertyFilter::property("p2").includes(vec![Prop::U64(6)]); - let results = search_edges(filter); - assert_eq!( - results, - vec![("2".into(), "1".into()), ("3".into(), "1".into())] - ); - - let filter = PropertyFilter::property("p2").includes(vec![Prop::U64(2), Prop::U64(6)]); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - #[test] - fn search_edges_for_property_not_in() { - let filter = PropertyFilter::property("p2").excludes(vec![Prop::U64(6)]); - let results = search_edges(filter); - assert_eq!( - results, - vec![("1".into(), "2".into()), ("2".into(), "3".into())] - ); - } - - #[test] - fn search_edges_for_property_is_some() { - let filter = PropertyFilter::property("p2").is_some(); - let results = search_edges(filter); - assert_eq!( - results, - vec![ - ("1".into(), "2".into()), - ("2".into(), "1".into()), - ("2".into(), "3".into()), - ("3".into(), "1".into()) - ] - ); - } - - // #[test] - // fn search_edges_for_property_is_none() { - // let filter = CompositeNodeFilter::Property(PropertyFilter::is_none("p2")); - // let results = search_nodes_by_composite_filter(&filter); - // assert_eq!(results, vec!["1"]); - // } - - #[test] - fn search_edge_by_src_dst() { - let filter = EdgeFilter::src().eq("3").and(EdgeFilter::dst().eq("1")); - - let results = search_edges(filter); - assert_eq!(results, vec![("3".into(), "1".into())]); - } - #[test] fn test_fuzzy_search() { - let filter = EdgeFilter::src().fuzzy_search("shiva", 2, false); - let results = fuzzy_search_edges_by_composite_filter(filter); + let filter = EdgeFilter::src().name().fuzzy_search("shiva", 2, false); + let results = fuzzy_search_edges(filter); assert_eq!(results, vec![("shivam".into(), "raphtory".into())]); - let filter = EdgeFilter::dst().fuzzy_search("pomet", 2, false); - let results = fuzzy_search_edges_by_composite_filter(filter); + let filter = EdgeFilter::dst().name().fuzzy_search("pomet", 2, false); + let results = fuzzy_search_edges(filter); assert_eq!(results, vec![("raphtory".into(), "pometry".into())]); } #[test] fn test_fuzzy_search_prefix_match() { - let filter = EdgeFilter::dst().fuzzy_search("pome", 2, false); - let results = fuzzy_search_edges_by_composite_filter(filter); + let filter = EdgeFilter::dst().name().fuzzy_search("pome", 2, false); + let results = fuzzy_search_edges(filter); assert_eq!(results, Vec::<(String, String)>::new()); - let filter = EdgeFilter::dst().fuzzy_search("pome", 2, true); - let results = fuzzy_search_edges_by_composite_filter(filter); + let filter = EdgeFilter::dst().name().fuzzy_search("pome", 2, true); + let results = fuzzy_search_edges(filter); assert_eq!(results, vec![("raphtory".into(), "pometry".into())]); } #[test] fn test_fuzzy_search_property() { let filter = PropertyFilter::property("p1").fuzzy_search("tano", 2, false); - let results = fuzzy_search_edges_by_composite_filter(filter); + let results = fuzzy_search_edges(filter); assert_eq!(results, vec![("shivam".into(), "raphtory".into())]); } #[test] fn test_fuzzy_search_property_prefix_match() { let filter = PropertyFilter::property("p1").fuzzy_search("charl", 1, false); - let results = fuzzy_search_edges_by_composite_filter(filter); + let results = fuzzy_search_edges(filter); assert_eq!(results, Vec::<(String, String)>::new()); let filter = PropertyFilter::property("p1").fuzzy_search("charl", 1, true); - let results = fuzzy_search_edges_by_composite_filter(filter); + let results = fuzzy_search_edges(filter); assert_eq!(results, vec![("raphtory".into(), "pometry".into())]); } } @@ -1556,7 +257,7 @@ mod search_tests { #[cfg(feature = "proto")] #[ignore = "this test is for experiments with the jira graph"] fn load_jira_graph() -> Result<(), GraphError> { - use crate::db::graph::views::property_filter::NodeFilterOps; + use crate::db::graph::views::filter::model::NodeFilterBuilderOps; global_info_logger(); let graph = Graph::decode("/tmp/graphs/jira").expect("failed to load graph"); assert!(graph.count_nodes() > 0); @@ -1565,9 +266,9 @@ mod search_tests { let elapsed = now.elapsed().unwrap().as_secs(); info!("indexing took: {:?}", elapsed); - graph.create_index().unwrap(); + graph.create_index_in_ram().unwrap(); - let filter = NodeFilter::node_name().eq("DEV-1690"); + let filter = NodeFilter::name().eq("DEV-1690"); let issues = graph.search_nodes(filter, 5, 0)?; assert!(!issues.is_empty()); diff --git a/raphtory/src/serialise/incremental.rs b/raphtory/src/serialise/incremental.rs index 2c492c2fcf..945f2c6f9b 100644 --- a/raphtory/src/serialise/incremental.rs +++ b/raphtory/src/serialise/incremental.rs @@ -1,4 +1,6 @@ use super::GraphFolder; +#[cfg(feature = "search")] +use crate::prelude::SearchableGraphOps; use crate::{ core::{ utils::errors::{GraphError, WriteError}, @@ -8,9 +10,9 @@ use crate::{ api::{storage::storage::Storage, view::MaterializedGraph}, graph::views::deletion_graph::PersistentGraph, }, - prelude::Graph, + prelude::{Graph, StableDecode}, serialise::{ - serialise::{CacheOps, StableDecode, StableEncode}, + serialise::{CacheOps, InternalStableDecode, StableEncode}, ProtoGraph, }, }; @@ -34,7 +36,7 @@ use tracing::instrument; pub struct GraphWriter { writer: Arc>, proto_delta: Mutex, - folder: GraphFolder, + pub(crate) folder: GraphFolder, } fn try_write(writer: &mut File, bytes: &[u8]) -> Result<(), WriteError> { @@ -292,17 +294,21 @@ impl InternalCache for MaterializedGraph { } } -impl CacheOps for G { +impl CacheOps for G { fn cache(&self, path: impl Into) -> Result<(), GraphError> { let folder = path.into(); self.encode(&folder)?; self.init_cache(&folder) } + #[instrument(level = "debug", skip(self))] fn write_updates(&self) -> Result<(), GraphError> { let cache = self.get_cache().ok_or(GraphError::CacheNotInnitialised)?; cache.write()?; - cache.folder.write_metadata(self) + cache.folder.write_metadata(self)?; + #[cfg(feature = "search")] + self.persist_index_to_disk(&cache.folder.root_folder)?; + Ok(()) } fn load_cached(path: impl Into) -> Result { diff --git a/raphtory/src/serialise/mod.rs b/raphtory/src/serialise/mod.rs index f89bf3d17e..57ef6a137b 100644 --- a/raphtory/src/serialise/mod.rs +++ b/raphtory/src/serialise/mod.rs @@ -11,12 +11,16 @@ mod proto { include!(concat!(env!("OUT_DIR"), "/serialise.rs")); } +#[cfg(feature = "search")] +use crate::prelude::SearchableGraphOps; use crate::{ - core::utils::errors::GraphError, db::api::view::MaterializedGraph, prelude::GraphViewOps, + core::utils::errors::GraphError, + db::api::view::{internal::InternalStorageOps, MaterializedGraph}, + prelude::GraphViewOps, serialise::metadata::GraphMetadata, }; pub use proto::Graph as ProtoGraph; -pub use serialise::{CacheOps, StableDecode, StableEncode}; +pub use serialise::{CacheOps, InternalStableDecode, StableDecode, StableEncode}; use std::{ fs::{self, File, OpenOptions}, io::{self, BufReader, ErrorKind, Read, Write}, @@ -92,7 +96,21 @@ impl GraphFolder { pub fn write_graph(&self, graph: &impl StableEncode) -> Result<(), GraphError> { self.write_graph_data(graph)?; - self.write_metadata(graph) + self.write_metadata(graph)?; + + #[cfg(feature = "search")] + self.write_index(graph)?; + + Ok(()) + } + + #[cfg(feature = "search")] + fn write_index(&self, graph: &impl StableEncode) -> Result<(), GraphError> { + if self.prefer_zip_format { + graph.persist_index_to_disk_zip(&self.root_folder) + } else { + graph.persist_index_to_disk(&self.root_folder) + } } fn write_graph_data(&self, graph: &impl StableEncode) -> Result<(), io::Error> { @@ -205,26 +223,16 @@ impl From<&GraphFolder> for GraphFolder { } // this mod focuses on the zip format, as the folder format is -// the default and is largelly exercised in other places +// the default and is largely exercised in other places #[cfg(test)] mod zip_tests { - use super::{StableDecode, StableEncode}; + use super::StableEncode; use crate::{ - prelude::{AdditionOps, CacheOps, Graph, GraphViewOps, NO_PROPS}, + prelude::{AdditionOps, CacheOps, Graph, NO_PROPS}, serialise::{metadata::GraphMetadata, GraphFolder}, }; use raphtory_api::core::utils::logging::global_info_logger; - #[test] - fn test_zip() { - let graph = Graph::new(); - graph.add_node(0, 0, NO_PROPS, None).unwrap(); - let temp_file = tempfile::NamedTempFile::new().unwrap(); - graph.encode(GraphFolder::new_as_zip(&temp_file)).unwrap(); - let graph = Graph::decode(&temp_file).unwrap(); - assert_eq!(graph.count_nodes(), 1); - } - #[test] fn test_load_cached_from_zip() { let graph = Graph::new(); @@ -238,13 +246,17 @@ mod zip_tests { #[test] fn test_read_metadata_from_noninitialized_zip() { global_info_logger(); + let graph = Graph::new(); graph.add_node(0, 0, NO_PROPS, None).unwrap(); + let temp_file = tempfile::NamedTempFile::new().unwrap(); let folder = GraphFolder::new_as_zip(&temp_file); folder.write_graph_data(&graph).unwrap(); + let err = folder.try_read_metadata(); assert!(err.is_err()); + let result = folder.read_metadata().unwrap(); assert_eq!( result, diff --git a/raphtory/src/serialise/serialise.rs b/raphtory/src/serialise/serialise.rs index 0860ff47af..a7ab602dc7 100644 --- a/raphtory/src/serialise/serialise.rs +++ b/raphtory/src/serialise/serialise.rs @@ -1,4 +1,6 @@ use super::{proto_ext::PropTypeExt, GraphFolder}; +#[cfg(feature = "search")] +use crate::prelude::SearchableGraphOps; use crate::{ core::{ entities::{graph::tgraph::TemporalGraph, LayerIds}, @@ -53,15 +55,32 @@ pub trait StableEncode: StaticGraphViewOps { } } -pub trait StableDecode: Sized { +pub trait StableDecode: InternalStableDecode + StaticGraphViewOps { + fn decode(path: impl Into) -> Result { + let folder = path.into(); + let graph = Self::decode_from_path(&folder)?; + + #[cfg(feature = "search")] + graph.load_index(&folder.root_folder)?; + + Ok(graph) + } +} + +impl StableDecode for T {} + +pub trait InternalStableDecode: Sized { fn decode_from_proto(graph: &proto::Graph) -> Result; + fn decode_from_bytes(bytes: &[u8]) -> Result { let graph = proto::Graph::decode(bytes)?; Self::decode_from_proto(&graph) } - fn decode(path: impl Into) -> Result { - let bytes = path.into().read_graph()?; - Self::decode_from_bytes(bytes.as_ref()) + + fn decode_from_path(path: &GraphFolder) -> Result { + let bytes = path.read_graph()?; + let graph = Self::decode_from_bytes(bytes.as_ref())?; + Ok(graph) } } @@ -253,7 +272,7 @@ impl StableEncode for MaterializedGraph { } } -impl StableDecode for TemporalGraph { +impl InternalStableDecode for TemporalGraph { fn decode_from_proto(graph: &proto::Graph) -> Result { let storage = Self::default(); graph.metas.par_iter().for_each(|meta| { @@ -607,7 +626,7 @@ fn unify_property_types( Ok((const_pt, temp_pt)) } -impl StableDecode for GraphStorage { +impl InternalStableDecode for GraphStorage { fn decode_from_proto(graph: &proto::Graph) -> Result { Ok(GraphStorage::Unlocked(Arc::new( TemporalGraph::decode_from_proto(graph)?, @@ -615,7 +634,7 @@ impl StableDecode for GraphStorage { } } -impl StableDecode for MaterializedGraph { +impl InternalStableDecode for MaterializedGraph { fn decode_from_proto(graph: &proto::Graph) -> Result { let storage = GraphStorage::decode_from_proto(graph)?; let graph = match graph.graph_type() { @@ -628,7 +647,7 @@ impl StableDecode for MaterializedGraph { } } -impl StableDecode for Graph { +impl InternalStableDecode for Graph { fn decode_from_proto(graph: &proto::Graph) -> Result { match graph.graph_type() { proto::GraphType::Event => { @@ -640,7 +659,7 @@ impl StableDecode for Graph { } } -impl StableDecode for PersistentGraph { +impl InternalStableDecode for PersistentGraph { fn decode_from_proto(graph: &proto::Graph) -> Result { match graph.graph_type() { proto::GraphType::Event => Err(GraphError::GraphLoadError), @@ -1456,4 +1475,252 @@ mod proto_test { Prop::from_arr::(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), )); } + + #[cfg(feature = "search")] + mod test_index_io { + use crate::{ + core::{utils::errors::GraphError, Prop}, + db::{ + api::{ + mutation::internal::{InternalAdditionOps, InternalPropertyAdditionOps}, + view::{internal::InternalStorageOps, StaticGraphViewOps}, + }, + graph::views::filter::model::{AsNodeFilter, NodeFilter, NodeFilterBuilderOps}, + }, + prelude::{ + AdditionOps, CacheOps, Graph, GraphViewOps, NodeViewOps, PropertyAdditionOps, + SearchableGraphOps, StableDecode, StableEncode, + }, + serialise::{incremental::InternalCache, GraphFolder}, + }; + use neo4rs::Path; + use raphtory_api::core::{storage::arc_str::ArcStr, utils::logging::global_info_logger}; + use std::path::PathBuf; + + fn init_graph(graph: G) -> G + where + G: StaticGraphViewOps + + AdditionOps + + InternalAdditionOps + + InternalPropertyAdditionOps + + PropertyAdditionOps, + { + graph + .add_node( + 1, + "Alice", + vec![("p1", Prop::U64(2u64))], + Some("fire_nation"), + ) + .unwrap(); + graph + } + + fn assert_search_results( + graph: &Graph, + filter: &T, + expected: Vec<&str>, + ) { + let res = graph + .search_nodes(filter.clone(), 2, 0) + .unwrap() + .into_iter() + .map(|n| n.name()) + .collect::>(); + assert_eq!(res, expected); + } + + #[test] + fn test_create_no_index_persist_no_index_on_encode_load_no_index_on_decode() { + // No index persisted since it was never created + let graph = init_graph(Graph::new()); + + let err = graph + .search_nodes(NodeFilter::name().eq("Alice"), 2, 0) + .expect_err("Expected error since index was not created"); + assert!(matches!(err, GraphError::IndexNotCreated)); + + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.encode(path).unwrap(); + + let graph = Graph::decode(path).unwrap(); + let index = graph.get_storage().unwrap().index.get(); + assert!(index.is_none()); + } + + #[test] + fn test_create_index_persist_index_on_encode_load_index_on_decode() { + let graph = init_graph(Graph::new()); + + // Created index + graph.create_index().unwrap(); + + let filter = NodeFilter::name().eq("Alice"); + assert_search_results(&graph, &filter, vec!["Alice"]); + + // Persisted both graph and index + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.encode(path).unwrap(); + + // Loaded index that was persisted + let graph = Graph::decode(path).unwrap(); + let index = graph.get_storage().unwrap().index.get(); + assert!(index.is_some()); + + assert_search_results(&graph, &filter, vec!["Alice"]); + } + + #[test] + fn test_create_index_persist_index_on_encode_update_index_load_persisted_index_on_decode() { + let graph = init_graph(Graph::new()); + + // Created index + graph.create_index().unwrap(); + + let filter1 = NodeFilter::name().eq("Alice"); + assert_search_results(&graph, &filter1, vec!["Alice"]); + + // Persisted both graph and index + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.encode(path).unwrap(); + + // Updated both graph and index + graph + .add_node( + 2, + "Tommy", + vec![("p1", Prop::U64(5u64))], + Some("water_tribe"), + ) + .unwrap(); + let filter2 = NodeFilter::name().eq("Tommy"); + assert_search_results(&graph, &filter2, vec!["Tommy"]); + + // Loaded index that was persisted + let graph = Graph::decode(path).unwrap(); + let index = graph.get_storage().unwrap().index.get(); + assert!(index.is_some()); + assert_search_results(&graph, &filter1, vec!["Alice"]); + assert_search_results(&graph, &filter2, Vec::<&str>::new()); + + // Updating and encode the graph and index should decode the updated the graph as well as index + // So far we have the index that was created and persisted for the first time + graph + .add_node( + 2, + "Tommy", + vec![("p1", Prop::U64(5u64))], + Some("water_tribe"), + ) + .unwrap(); + let filter2 = NodeFilter::name().eq("Tommy"); + assert_search_results(&graph, &filter2, vec!["Tommy"]); + + // Should persist the updated graph and index + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.encode(path).unwrap(); + + // Should load the updated graph and index + let graph = Graph::decode(path).unwrap(); + let index = graph.get_storage().unwrap().index.get(); + assert!(index.is_some()); + assert_search_results(&graph, &filter1, vec!["Alice"]); + assert_search_results(&graph, &filter2, vec!["Tommy"]); + } + + #[test] + fn test_zip_encode_decode_index() { + let graph = init_graph(Graph::new()); + graph.create_index().unwrap(); + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + let folder = GraphFolder::new_as_zip(path); + graph.encode(folder.root_folder).unwrap(); + + let graph = Graph::decode(path).unwrap(); + let node = graph.node("Alice").unwrap(); + let node_type = node.node_type(); + assert_eq!(node_type, Some(ArcStr::from("fire_nation"))); + + let filter = NodeFilter::name().eq("Alice"); + assert_search_results(&graph, &filter, vec!["Alice"]); + } + + #[test] + fn test_create_index_in_ram() { + global_info_logger(); + + let graph = init_graph(Graph::new()); + graph.create_index_in_ram().unwrap(); + + let filter = NodeFilter::name().eq("Alice"); + assert_search_results(&graph, &filter, vec!["Alice"]); + + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.encode(path).unwrap(); + + let graph = Graph::decode(path).unwrap(); + let index = graph.get_storage().unwrap().index.get(); + assert!(index.is_none()); + + let results = graph.search_nodes(filter.clone(), 2, 0); + assert!(matches!(results, Err(GraphError::IndexNotCreated))); + } + + #[test] + fn test_cached_graph_view() { + global_info_logger(); + let graph = init_graph(Graph::new()); + graph.create_index().unwrap(); + + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.cache(path).unwrap(); + + graph + .add_node( + 2, + "Tommy", + vec![("p1", Prop::U64(5u64))], + Some("water_tribe"), + ) + .unwrap(); + graph.write_updates().unwrap(); + + let graph = Graph::decode(path).unwrap(); + let filter = NodeFilter::name().eq("Tommy"); + assert_search_results(&graph, &filter, vec!["Tommy"]); + } + + #[test] + fn test_cached_graph_view_create_index_after_graph_is_cached() { + global_info_logger(); + let graph = init_graph(Graph::new()); + + let binding = tempfile::TempDir::new().unwrap(); + let path = binding.path(); + graph.cache(path).unwrap(); + // Creates index in a temp dir within graph dir + graph.create_index().unwrap(); + + graph + .add_node( + 2, + "Tommy", + vec![("p1", Prop::U64(5u64))], + Some("water_tribe"), + ) + .unwrap(); + graph.write_updates().unwrap(); + + let graph = Graph::decode(path).unwrap(); + let filter = NodeFilter::name().eq("Tommy"); + assert_search_results(&graph, &filter, vec!["Tommy"]); + } + } }