diff --git a/docs/reference/gqlalchemy/vendors/memgraph.md b/docs/reference/gqlalchemy/vendors/memgraph.md index 22d984bb..6e4a515c 100644 --- a/docs/reference/gqlalchemy/vendors/memgraph.md +++ b/docs/reference/gqlalchemy/vendors/memgraph.md @@ -340,3 +340,38 @@ Terminate transactions in the database. - `List[MemgraphTerminatedTransaction]` - A list of MemgraphTerminatedTransaction objects with info on their status. +#### analyze\_graph + +```python +def analyze_graph(labels: Optional[List[str]] = None) -> List[dict] +``` + +Analyze graph to calculate statistics for better index selection. + +Calculates distribution properties of a graph to enable the database +to select more optimal indexes and MERGE operations. + +**Arguments**: + +- `labels` _Optional[List[str]]_ - Optional list of labels to analyze. If None, analyzes all labels. + +**Returns**: + +- `List[dict]` - A list of dictionaries containing analysis results with keys: label, property, num estimation nodes, num groups, avg group size, chi-squared value, avg degree. + +#### delete\_graph\_statistics + +```python +def delete_graph_statistics(labels: Optional[List[str]] = None) -> List[dict] +``` + +Delete graph statistics previously calculated by analyze_graph. + +**Arguments**: + +- `labels` _Optional[List[str]]_ - Optional list of labels to delete statistics for. If None, deletes statistics for all labels. + +**Returns**: + +- `List[dict]` - A list of dictionaries containing deleted index info with keys: label, property. + diff --git a/gqlalchemy/vendors/memgraph.py b/gqlalchemy/vendors/memgraph.py index c900c6a4..0e4c6e3e 100644 --- a/gqlalchemy/vendors/memgraph.py +++ b/gqlalchemy/vendors/memgraph.py @@ -569,3 +569,53 @@ def terminate_transactions(self, transaction_ids: List[str]) -> List[MemgraphTer terminated_transactions = list(map(create_terminated_transaction, transactions_data)) return terminated_transactions + + def analyze_graph(self, labels: Optional[List[str]] = None) -> List[dict]: + """Analyze graph to calculate statistics for better index selection. + + Calculates distribution properties of a graph to enable the database + to select more optimal indexes and MERGE operations. + + Args: + labels: Optional list of labels to analyze. If None, analyzes all labels. + + Returns: + List[dict]: A list of dictionaries containing analysis results with keys: + - label: Index's label + - property: Index's property + - num estimation nodes: Nodes used for estimation + - num groups: Distinct property values + - avg group size: Average group size per value + - chi-squared value: Statistical distribution measure + - avg degree: Average degree of indexed nodes + """ + if labels: + labels_str = ", ".join([f":{label}" for label in labels]) + query = f"ANALYZE GRAPH ON LABELS {labels_str};" + else: + query = "ANALYZE GRAPH;" + + return list(self.execute_and_fetch(query)) + + def delete_graph_statistics(self, labels: Optional[List[str]] = None) -> List[dict]: + """Delete graph statistics previously calculated by analyze_graph. + + Use this to reset the analysis data if you want to recalculate statistics + after significant changes to the graph structure or data. + + Args: + labels: Optional list of labels to delete statistics for. + If None, deletes statistics for all labels. + + Returns: + List[dict]: A list of dictionaries containing deleted index info with keys: + - label: The deleted index's label + - property: The deleted index's property + """ + if labels: + labels_str = ", ".join([f":{label}" for label in labels]) + query = f"ANALYZE GRAPH ON LABELS {labels_str} DELETE STATISTICS;" + else: + query = "ANALYZE GRAPH DELETE STATISTICS;" + + return list(self.execute_and_fetch(query)) diff --git a/tests/memgraph/test_analyze_graph.py b/tests/memgraph/test_analyze_graph.py new file mode 100644 index 00000000..c575fefb --- /dev/null +++ b/tests/memgraph/test_analyze_graph.py @@ -0,0 +1,87 @@ +# Copyright (c) 2016-2022 Memgraph Ltd. [https://memgraph.com] +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest.mock import patch + +from gqlalchemy.vendors.memgraph import Memgraph + + +def test_analyze_graph_all_labels(): + """Test analyze_graph without specifying labels.""" + memgraph = Memgraph() + mock_result = [ + { + "label": "Person", + "property": "name", + "num estimation nodes": 100, + "num groups": 50, + "avg group size": 2.0, + "chi-squared value": 0.5, + "avg degree": 3.0, + } + ] + + with patch.object(Memgraph, "execute_and_fetch", return_value=iter(mock_result)) as mock: + result = memgraph.analyze_graph() + + mock.assert_called_with("ANALYZE GRAPH;") + assert result == mock_result + + +def test_analyze_graph_specific_labels(): + """Test analyze_graph with specific labels.""" + memgraph = Memgraph() + mock_result = [] + + with patch.object(Memgraph, "execute_and_fetch", return_value=iter(mock_result)) as mock: + result = memgraph.analyze_graph(labels=["Person", "Company"]) + + mock.assert_called_with("ANALYZE GRAPH ON LABELS :Person, :Company;") + assert result == mock_result + + +def test_analyze_graph_single_label(): + """Test analyze_graph with a single label.""" + memgraph = Memgraph() + mock_result = [] + + with patch.object(Memgraph, "execute_and_fetch", return_value=iter(mock_result)) as mock: + result = memgraph.analyze_graph(labels=["Person"]) + + mock.assert_called_with("ANALYZE GRAPH ON LABELS :Person;") + assert result == mock_result + + +def test_delete_graph_statistics_all(): + """Test delete_graph_statistics for all labels.""" + memgraph = Memgraph() + mock_result = [{"label": "Person", "property": "name"}] + + with patch.object(Memgraph, "execute_and_fetch", return_value=iter(mock_result)) as mock: + result = memgraph.delete_graph_statistics() + + mock.assert_called_with("ANALYZE GRAPH DELETE STATISTICS;") + assert result == mock_result + + +def test_delete_graph_statistics_specific_labels(): + """Test delete_graph_statistics for specific labels.""" + memgraph = Memgraph() + mock_result = [] + + with patch.object(Memgraph, "execute_and_fetch", return_value=iter(mock_result)) as mock: + result = memgraph.delete_graph_statistics(labels=["Person", "Company"]) + + mock.assert_called_with("ANALYZE GRAPH ON LABELS :Person, :Company DELETE STATISTICS;") + assert result == mock_result