erpc_analysis/graph/
projections.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! This module is responsible for generating Cypher query strings
//! specifically for interacting with Neo4j Graph Data Science (GDS)
//! library, focusing on graph projection tasks.

use std::collections::HashMap;

/// Builds the Cypher query string for dropping a GDS graph projection.
/// This is used to ensure idempotency when creating projections.
///
/// # Arguments
/// * `projection_name` - The name of the GDS graph projection to be dropped.
///
/// # Returns
/// A String containing the fully formed Cypher query for dropping
/// the projection.
pub fn build_gds_drop_cypher(projection_name: &str) -> String {
    format!(
        "CALL gds.graph.drop('{}', false) YIELD graphName",
        projection_name
    )
}

/// Builds the Cypher query string for creating (projecting) an in-memory
/// GDS graph.
///
/// # Arguments
/// * `projection_name` - The desired name for the in-memory graph projection.
/// * `node_label` - The label of the nodes from the source graph to include
///   in the projection.
/// * `relationship_types` - A map where keys are relationship types (String)
///   from the source graph and values are their desired orientation in the
///   projection (e.g., "NATURAL","REVERSE", "UNDIRECTED").
/// * `relationship_properties_to_project` - Optional. A slice of relationship
///   property keys.
///
/// # Returns
/// A String containing the fully formed Cypher query for creating the
/// GDS graph projection.
pub fn build_gds_project_cypher(
    projection_name: &str,
    node_label: &str,
    relationship_types: &HashMap<String, String>,
    // Parameter kept for future use
    _relationship_properties_to_project: Option<&[String]>,
) -> String {
    let relationships_cypher_map_entries: Vec<String> = relationship_types
        .iter()
        .map(|(rel_type, orientation)| {
            format!("{}: {{ orientation: '{}' }}", rel_type, orientation)
        })
        .collect();

    let relationships_cypher_map =
        format!("{{ {} }}", relationships_cypher_map_entries.join(", "));

    let relationship_properties_cypher_map = "{}";

    format!(
        "CALL gds.graph.project('{}', '{}', {}, {}) YIELD graphName, \
         nodeCount, relationshipCount, projectMillis",
        projection_name,
        node_label,
        relationships_cypher_map,
        relationship_properties_cypher_map
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_gds_drop_cypher() {
        let projection_name = "test_projection";
        let result = build_gds_drop_cypher(projection_name);
        let expected =
            "CALL gds.graph.drop('test_projection', false) YIELD graphName";

        assert_eq!(result, expected);
    }

    #[test]
    fn test_build_gds_drop_cypher_with_special_characters() {
        let projection_name = "test-projection_123";
        let result = build_gds_drop_cypher(projection_name);
        let expected = "CALL gds.graph.drop('test-projection_123', false) \
                        YIELD graphName";

        assert_eq!(result, expected);
    }

    #[test]
    fn test_build_gds_project_cypher_single_relationship() {
        let projection_name = "tor_network";
        let node_label = "Relay";
        let mut relationship_types = HashMap::new();
        relationship_types
            .insert("CIRCUIT_SUCCESS".to_string(), "NATURAL".to_string());

        let result = build_gds_project_cypher(
            projection_name,
            node_label,
            &relationship_types,
            None,
        );

        let expected = "CALL gds.graph.project('tor_network', 'Relay', \
                        { CIRCUIT_SUCCESS: { orientation: 'NATURAL' } }, {}) \
                        YIELD graphName, nodeCount, relationshipCount, \
                        projectMillis";

        assert_eq!(result, expected);
    }

    #[test]
    fn test_build_gds_project_cypher_multiple_relationships() {
        let projection_name = "complex_network";
        let node_label = "Node";
        let mut relationship_types = HashMap::new();
        relationship_types
            .insert("SUCCESS".to_string(), "NATURAL".to_string());
        relationship_types
            .insert("FAILURE".to_string(), "REVERSE".to_string());
        relationship_types
            .insert("BIDIRECTIONAL".to_string(), "UNDIRECTED".to_string());

        let result = build_gds_project_cypher(
            projection_name,
            node_label,
            &relationship_types,
            None,
        );

        // Since HashMap iteration order is not guaranteed, check that all
        // parts are present
        assert!(result
            .contains("CALL gds.graph.project('complex_network', 'Node'"));
        assert!(result.contains("SUCCESS: { orientation: 'NATURAL' }"));
        assert!(result.contains("FAILURE: { orientation: 'REVERSE' }"));
        assert!(
            result.contains("BIDIRECTIONAL: { orientation: 'UNDIRECTED' }")
        );
        assert!(result.contains(
            "YIELD graphName, nodeCount, relationshipCount, projectMillis"
        ));
    }

    #[test]
    fn test_build_gds_project_cypher_empty_relationships() {
        let projection_name = "empty_rels";
        let node_label = "EmptyNode";
        let relationship_types = HashMap::new();

        let result = build_gds_project_cypher(
            projection_name,
            node_label,
            &relationship_types,
            None,
        );

        let expected = "CALL gds.graph.project('empty_rels', 'EmptyNode', \
                        {  }, {}) YIELD graphName, nodeCount, \
                        relationshipCount, projectMillis";

        assert_eq!(result, expected);
    }
}