diff --git a/mage/data/query-modules/cpp/refactor/categorize1.png b/mage/data/query-modules/cpp/refactor/categorize1.png new file mode 100644 index 00000000000..950b3034fc6 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/categorize1.png differ diff --git a/mage/data/query-modules/cpp/refactor/categorize2.png b/mage/data/query-modules/cpp/refactor/categorize2.png new file mode 100644 index 00000000000..0c7b38c31f1 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/categorize2.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonenodes1.png b/mage/data/query-modules/cpp/refactor/clonenodes1.png new file mode 100644 index 00000000000..fc78aac58ac Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonenodes1.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonenodes2.png b/mage/data/query-modules/cpp/refactor/clonenodes2.png new file mode 100644 index 00000000000..d3439cc018c Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonenodes2.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonesubgraph1.png b/mage/data/query-modules/cpp/refactor/clonesubgraph1.png new file mode 100644 index 00000000000..ebeb8af2ddb Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonesubgraph1.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonesubgraph2.png b/mage/data/query-modules/cpp/refactor/clonesubgraph2.png new file mode 100644 index 00000000000..6a6de8f4320 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonesubgraph2.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonesubgraphpath1.png b/mage/data/query-modules/cpp/refactor/clonesubgraphpath1.png new file mode 100644 index 00000000000..cb0be9d788b Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonesubgraphpath1.png differ diff --git a/mage/data/query-modules/cpp/refactor/clonesubgraphpath2.png b/mage/data/query-modules/cpp/refactor/clonesubgraphpath2.png new file mode 100644 index 00000000000..6c45adafd19 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/clonesubgraphpath2.png differ diff --git a/mage/data/query-modules/cpp/refactor/graph_after.png b/mage/data/query-modules/cpp/refactor/graph_after.png new file mode 100644 index 00000000000..17eab759814 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/graph_after.png differ diff --git a/mage/data/query-modules/cpp/refactor/graph_before.png b/mage/data/query-modules/cpp/refactor/graph_before.png new file mode 100644 index 00000000000..2f48c7563d1 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/graph_before.png differ diff --git a/mage/data/query-modules/cpp/refactor/rel_after.png b/mage/data/query-modules/cpp/refactor/rel_after.png new file mode 100644 index 00000000000..08146b8a641 Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/rel_after.png differ diff --git a/mage/data/query-modules/cpp/refactor/rel_before.png b/mage/data/query-modules/cpp/refactor/rel_before.png new file mode 100644 index 00000000000..fca231ebfcb Binary files /dev/null and b/mage/data/query-modules/cpp/refactor/rel_before.png differ diff --git a/mage/query-modules/cpp/refactor.md b/mage/query-modules/cpp/refactor.md new file mode 100644 index 00000000000..faf61e926d5 --- /dev/null +++ b/mage/query-modules/cpp/refactor.md @@ -0,0 +1,700 @@ +--- +id: refactor +title: refactor +sidebar_label: refactor +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import RunOnSubgraph from '../../templates/_run_on_subgraph.mdx'; + +export const Highlight = ({children, color}) => ( + +{children} + +); + +The `refactor` module provides utilities for changing nodes and relationships. + +[![docs-source](https://img.shields.io/badge/source-text-FB6E00?logo=github&style=for-the-badge)](https://github.com/memgraph/mage/tree/main/cpp/refactor_module) + +| Trait | Value | +| ------------------- | ----------------------------------------------------- | +| **Module type** | **algorithm** | +| **Implementation** | **C++** | +| **Parallelism** | **sequential** | + +### Procedures + +### `from(relationship, new_from)` + +Redirect the relationship to use a new start (from) node. + +#### Input: + +- `relationship: Relationship` ➡ the relationship to be modified. +- `new_from: Node` ➡ new start (from) node. + +#### Output: + +- `relationship` - the modified relationship. + +#### Usage: + +```cypher +MERGE (ivan:Person {name: "Ivan"}) MERGE (matija:Person {name: "Matija"}) MERGE (diora:Person {name:"Idora"}) CREATE (ivan)-[:Friends]->(matija); +``` +The following query changes the the relationship `Ivan ➡ Matija` to `Idora ➡ Matija`. +```cypher +MATCH (:Person {name: "Ivan"})-[rel:Friends]->(:Person {name: "Matija"}) MATCH (idora: Person {name:"Idora"}) CALL refactor.from(rel, idora) YIELD relationship RETURN relationship; +``` + +### `to(relationship, new_to)` + +Redirect the relationship to use a new end (to) node. + +#### Input: + +- `relationship: Relationship` ➡ the relationship to be modified. +- `new_to: Node` ➡ new end (to) node. + +#### Output: + +- `relationship` - the modified relationship. + +#### Usage: + +```cypher +MERGE (ivan:Person {name: "Ivan"}) MERGE (matija:Person {name: "Matija"}) MERGE (diora:Person {name:"Idora"}) CREATE (ivan)-[:Friends]->(matija); +``` +The following query changes the the relationship `Ivan ➡ Matija` to `Ivan ➡ Idora`. +```cypher +MATCH (:Person {name: "Ivan"})-[rel:Friends]->(:Person {name: "Matija"}) MATCH (idora: Person {name:"Idora"}) CALL refactor.to(rel, idora) YIELD relationship RETURN relationship; +``` + +### `rename_label(old_label, new_label, nodes)` + +Rename a label from `old_label` to `new_label` for all nodes. If `nodes` is provided renaming is applied only to the given nodes. If a node doesn't contain the `old_label` the procedure doesn't modify it. + +#### Input: + +- `old_label: str` ➡ old label name. +- `new_label: str` ➡ new label name. +- `nodes: List[Node]` ➡ list of nodes to be modified. + +### Output: + +- `nodes_changed: int` ➡ number of modified nodes. + +#### Usage: + +```cypher +CREATE (:Node1 {title: "Node1"}) CREATE (:Node2 {title: "Node2"}) CREATE (:Node1); +``` +The following query changes the label of the first node to `Node` +```cypher +MATCH(n) WITH collect(n) AS nodes CALL refactor.rename_label("Node1", "Node3", nodes) YIELD nodes_changed RETURN nodes_changed; +``` +```plaintext ++----------------------------+ +| nodes_changed | ++----------------------------+ +| 2 | ++----------------------------+ +``` + +### `rename_node_property(old_property, new_property, nodes)` + +Rename a property from `old_property` to `new_property` for all nodes. If `nodes` is provided renaming is applied only to the given nodes. If a node doesn't contain the `old_property` the procedure doesn't modify it. + +#### Input: + +- `old_property: str` ➡ old property name. +- `new_label: str` ➡ new property name. +- `nodes: List[Node]` ➡ list of nodes to be modified. + +### Output: + +- `nodes_changed: int` ➡ number of modified nodes. + +#### Usage: + +```cypher +CREATE (:Node1 {title: "Node1"}) CREATE (:Node2 {description: "Node2"}) CREATE (:Node3) CREATE (:Node4 {title: "title", description: "description"}); +``` +The following query will modify `Node1` and `Node4`. +```cypher +MATCH(n) WITH collect(n) AS nodes CALL refactor.rename_node_property("title", "description", nodes) YIELD nodes_changed RETURN nodes_changed; +``` +```plaintext ++----------------------------+ +| nodes_changed | ++----------------------------+ +| 2 | ++----------------------------+ +``` + +### `categorize(original_prop_key, rel_type, is_outgoing, new_label, new_prop_name_key, copy_props_list)` + +Generates a new category of nodes based on a specific property key from the existing nodes in the graph. Then, it creates relationships between the original and new category nodes to organize a graph based on these categories. + +#### Input: + +- `original_prop_key: string` ➡ the property key on the existing nodes used to determine the category. +- `rel_type: string` ➡ the type of relationship to be created between the original nodes and the new category nodes. +- `is_outgoing: bool` ➡ determines the direction of the new relationships. If 'true', relationships will be created from the original node to the category node. If 'false', they'll be created from the category node to the original node. +- `new_label: string` ➡ the label to be assigned to the new category nodes. +- `new_prop_name_key: string` ➡ the key for the new category node's property whose value will be the same as `original_prop_key` value. +- `copy_props_list: List[string] (default = [])` ➡ an array of property keys from the original nodes to be copied to the new category nodes. + + +#### Output: + +- `status: string` ➡ "success" if no errors were generated. + +#### Usage: + +```cypher +CALL refactor.categorize('genre', 'GENRE', false, "Genre", "name") YIELD status RETURN status; +``` + +```plaintext ++----------------------------+ +| status | ++----------------------------+ +| success | ++----------------------------+ +``` + + +### `clone_nodes(nodes, clone_rels, skip_props)` + +Clones specific nodes in the graph, preserving their original labels, properties and optionally relationships. Offers the flexibility to exclude specific node properties during the cloning process. + +#### Input: + +- `nodes: List[Node]` ➡ a list of nodes intended for duplication. +- `clone_rels: bool (default = false)` ➡ if set to true, the function will also clone the relationships of the original nodes, connecting them to the cloned nodes in the same manner. +- `skip_props: List[string] (default = [])` ➡ a list of property keys, properties associated with these keys will be skipped during the cloning process. + + +#### Output: + +- `cloned_node_id: int` ➡ ID of the original node which was cloned. +- `new_node: Node` ➡ new cloned node. + +#### Usage: + +```cypher +MATCH (a:Person {name: "Ana", age: 22, id:0}) +CALL refactor.clone_nodes([a], False, ["age", "id"]) +YIELD cloned_node_id, new_node RETURN cloned_node_id, new_node; +``` + +```plaintext ++----------------------------+----------------------------+ +| cloned_node_id | new_node | ++----------------------------+----------------------------+ +| 0 | { | +| | "id": 1, | +| | "labels": [ | +| | "Person" | +| | ], | +| | "properties": { | +| | name: "Ana" | +| | }, | +| | "type": "node" | +| | } | ++----------------------------+----------------------------+ +``` + + +### `clone_subgraph(nodes, rels, config)` + +Clones the subgraph by cloning the given list of nodes and relationships. If no relationships are provided, all existing relationships between the given nodes will be cloned. + +#### Input: + +- `nodes: List[Node]` ➡ a list of nodes which form the subgraph intended for duplication. +- `rels: List[Relationship] (default = [])` ➡ a list of relationships intended for duplication. If this list is empty or not provided, all existing relationships between the given nodes will be cloned. +- `config: Map (default = {})` ➡ configuration parameters explained below. + +#### Parameters: + + | Name | Type | Default | Description | + |- |- |- |- | + | skipProperties | List | [ ] | A list of property keys. Properties associated with these keys will be skipped during the cloning process. Therefore, new nodes will not have them.| + | standinNodes | List | [ ] | A list of pairs (lists with only two elements) where: 1) The first element is the original node (from the input nodes list) that you're planning to clone. 2) The second element is an existing node in the graph that will "stand in" or replace the clone of the original node in the resultant subgraph. | + + +#### Output: + +- `cloned_node_id: int` ➡ ID of the original node which was cloned. +- `new_node: Node` ➡ new cloned node. + +#### Usage: + +```cypher +MATCH (ana:Ana {id: 0}), (marija:Marija {id: 1}) +MATCH (ana)-[r1:KNOWS]->(marija) +CALL refactor.clone_subgraph([ana, marija], [r1], {standinNodes: [[ana, marija]]}) +YIELD cloned_node_id, new_node RETURN cloned_node_id, new_node; +``` + +```plaintext ++----------------------------+----------------------------+ +| cloned_node_id | new_node | ++----------------------------+----------------------------+ +| 1 | { | +| | "id": 2, | +| | "labels": [ | -> node :Ana was not cloned +| | "Marija" | because :Marija is its +| | ], | "stand-in" node +| | "properties": {}, | +| | "type": "node" | +| | } | ++----------------------------+----------------------------+ +``` + +For better understanding, check the examples at the bottom of the page. + + +### `clone_subgraph_from_paths(paths, config)` + +Clones the subgraph specified by a specific list of paths. A path is a series of nodes connected by relationships. + +#### Input: + +- `paths: List[Path]` ➡ a list of paths which define the subgraph intended for duplication. +- `config: Map (default = {})` ➡ configuration parameters explained below. + +#### Parameters: + + | Name | Type | Default | Description | + |- |- |- |- | + | skipProperties | List | [ ] | A list of property keys. Properties associated with these keys will be skipped during the cloning process. Therefore, new nodes will not have them.| + | standinNodes | List | [ ] | A list of pairs (lists with only two elements) where the first element is the original node (from the input nodes list) that you're planning to clone, and the second element is an existing node in the graph that will "stand-in" or replace the clone of the original node in the resultant subgraph. | + + +#### Output: + +- `cloned_node_id: int` ➡ ID of the original node which was cloned. +- `new_node: Node` ➡ new cloned node. + +#### Usage: + +```cypher +MATCH (ana:Ana {id: 0}), (marija:Marija {id: 1}) +MATCH path = (ana)-[:KNOWS*]->(marija) +CALL refactor.clone_subgraph_from_paths([path], {standinNodes:[[ana, marija]]}) +YIELD cloned_node_id, new_node RETURN cloned_node_id, new_node; +``` + +```plaintext ++----------------------------+----------------------------+ +| cloned_node_id | new_node | ++----------------------------+----------------------------+ +| 1 | { | +| | "id": 2, | +| | "labels": [ | -> node :Ana was not cloned +| | "Marija" | because :Marija is its +| | ], | "stand-in" node +| | "properties": {}, | +| | "type": "node" | +| | } | ++----------------------------+----------------------------+ +``` + +For better understanding, check the examples at the bottom of the page. + +## Example - refactor.categorize + + + + + +You can create a simple graph database by running the following queries: + +```cypher +CREATE (a:Movie {id: 0, name: "MovieName", genre: "Drama"}) +CREATE (b:Book {id: 1, name: "BookName1", genre: "Drama", propertyToCopy: "copy me"}) +CREATE (c:Book {id: 2, name: "BookName2", genre: "Romance"}); +``` + + + + + +The image below shows the above data as a graph: + + + + + + + +```cypher +CALL refactor.categorize('genre', 'GENRE', true, "Genre", "name", ["propertyToCopy"]) +YIELD status RETURN status; +``` + + + + + +The results should be identical to the ones in the graph below, except for the +`id` values, which depend on the internal database `id` values: + + + + + + + + +## Example - refactor.clone_nodes + + + + + +You can create a simple graph database by running the following queries: + +```cypher +CREATE (a:Ana {name: "Ana", age: 22}) +CREATE (b:Marija {name: "Marija", age: 20}) +CREATE (a)-[r:KNOWS]->(b); +``` + + + + + +The image below shows the above data as a graph: + + + + + + + +```cypher +MATCH (a:Ana) MATCH (b:Marija) +CALL refactor.clone_nodes([a, b], True, ["age"]) YIELD * RETURN *; +``` + + + + + +The results should be identical to the ones in the graph below, except for the +`id` values, which depend on the internal database `id` values: + + + + + + + +## Example - refactor.clone_subgraph + + + + + +You can create a simple graph database by running the following queries: + +```cypher +MERGE (ana:Ana{name:'Ana'}) +MERGE (marija:Marija{name:'Marija'}) +MERGE (p2:Person{name:'person2'}) +MERGE (p3:Person{name:'person3'}) +MERGE (p4:Person{name:'person4'}) +MERGE (p5:Person{name:'person5'}) +MERGE (p6:Person{name:'person6'}) +CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) +CREATE (p4)<-[:KNOWS]-(p5) +CREATE (marija)-[:KNOWS]->(p6); +``` + + + + + +The image below shows the above data as a graph: + + + + + + + +```cypher +MATCH (ana:Ana), + (p2:Person{name: "person2"}), + (p3:Person{name: "person3"}), + (p4:Person{name: "person4"}), + (p5:Person{name: "person5"}) +CALL refactor.clone_subgraph([ana, p2, p3, p4, p5], [], { + standinNodes:[[ana, marija]] + }) +YIELD * RETURN *; +``` + + + + + +The results should be identical to the ones in the graph below, except for the +`id` values, which depend on the internal database `id` values. Note that the +whole subgraph was cloned except for node `:Ana` because node `:Marija` was +used as its "stand-in" node. + + + + + + + + +## Example - refactor.clone_subgraph_from_path + + + + + +You can create a simple graph database by running the following queries: + +```cypher +MERGE (ana:Ana{name:'Ana'}) +MERGE (marija:Marija{name:'Marija'}) +MERGE (p2:Person{name:'person2'}) +MERGE (p3:Person{name:'person3'}) +MERGE (p4:Person{name:'person4'}) +MERGE (p5:Person{name:'person5'}) +MERGE (p6:Person{name:'person6'}) +CREATE (ana)-[:KNOWS]->(p2)-[:KNOWS]->(p3)-[:KNOWS]->(p4) +CREATE (p4)<-[:KNOWS]-(p5) CREATE (p5)-[:LOVES]->(p6) +CREATE (marija)-[:KNOWS]->(p6); +``` + + + + + +The image below shows the above data as a graph: + + + + + + + +```cypher +MATCH (ana:Ana), + (marija:Marija) +MATCH path = (ana)-[:KNOWS*]->(node) +WITH ana, marija, collect(path) as paths +CALL refactor.clone_subgraph_from_paths(paths, { + standinNodes:[[ana, marija]] +}) +YIELD cloned_node_id, new_node +RETURN cloned_node_id, new_node; +``` + + + + + +The results should be identical to the ones in the graph below, except for the +`id` values, which depend on the internal database `id` values. Note that the +whole subgraph was cloned except for the node `:Ana` because node `:Marija` was +used as its "stand-in" node. + + + + + + + +### `collapse_node(nodes, type)` + +Collapses a node into a relationship. Can only collapse nodes with exactly 1 in relationship, and exactly 1 out relationship. The node must not have any self relationships. Collapsing any node that doesn't satisfy these requirements results in an exception. + +#### Input: + +- `nodes: Any` ➡ a node, node ID, or list of nodes and node IDs. +- `type: string` ➡ the type of the new relationship. + +#### Output: + +- `id_collapsed: integer` ➡ ID of the collapsed node. +- `new_relationship: integer` ➡ newly created relationship. + +#### Usage: + + + + + +```cypher +CREATE (c:Car)-[t:DRIVES_ON]->(r:Road)-[l:LEADS_TO]->(ci:City); +``` + + + + + + + + + + + +```cypher +MATCH (r:Road) +CALL refactor.collapse_node(r, "DRIVES_TO") YIELD id_collapsed, new_relationship RETURN id_collapsed, new_relationship; +``` + + + + + +```plaintext ++-------------------------------------------------------------------------------------------------------------------+ +| id_collapsed | new_relationship | ++-------------------------------------------------------------------------------------------------------------------+ +| 1 | {"id": 2,"start": 0,"end": 2,"label": "DRIVES_TO","properties": {},"type": "relationship"} | ++-------------------------------------------------------------------------------------------------------------------+ +``` + + + + + + + + + + + +### `invert(relationship)` + +Invert the direction of the given relationship. + +#### Input: + +- `relationship: Any` ➡ relationship, relationship ID, or a list of relationships or relationship IDs. + +#### Output: + +- `id_inverted: integer` ➡ ID of the inverted relationship. +- `relationship` ➡ the inverted relationship. + +#### Usage: + + + + + + +```cypher +CREATE (d:Dog)-[l:LOVES]->(h:Human); +``` + + + + + + + + + + +```cypher +MATCH (d:Dog)-[l:LOVES]->(h:Human) +CALL refactor.invert(l) YIELD id_inverted, relationship RETURN id_inverted, relationship; +``` + + + + +```plaintext ++-------------------------------------------------------------------------------------------------------------------+ +| id_inverted | relationship | ++-------------------------------------------------------------------------------------------------------------------+ +| 0 | {"id": 0,"start": 1,"end": 0,"label": "LOVES","properties": {},"type": "relationship"} | ++-------------------------------------------------------------------------------------------------------------------+ +``` + + + + + + + + + + \ No newline at end of file diff --git a/mage/templates/_mage_spells.mdx b/mage/templates/_mage_spells.mdx index d0b89999366..6ea52a783da 100644 --- a/mage/templates/_mage_spells.mdx +++ b/mage/templates/_mage_spells.mdx @@ -65,6 +65,7 @@ | [meta_util](/mage/query-modules/python/meta-util) | Python | A module that contains procedures describing graphs on a meta-level. | | [migrate](/mage/query-modules/python/migrate) | Python | A module that can access data from a MySQL, SQL Server or Oracle database. | | [periodic](/mage/query-modules/cpp/periodic) | C++ | A module containing procedures for periodically running difficult and/or memory/time consuming queries. | +| [refactor](/mage/query-modules/cpp/refactor) | C++ | The refactor module provides utilities for changing nodes and relationships. | | rust_example | Rust | Example of a basic module with input parameters forwarding, made in Rust. | | [uuid_generator](/mage/query-modules/cpp/uuid-generator) | C++ | A module that generates a new universally unique identifier (UUID). | diff --git a/sidebars/sidebarsMAGE.js b/sidebars/sidebarsMAGE.js index 229d7e59b8b..3d3559cbb73 100644 --- a/sidebars/sidebarsMAGE.js +++ b/sidebars/sidebarsMAGE.js @@ -63,6 +63,7 @@ module.exports = { "query-modules/cpp/pagerank", "query-modules/cpp/pagerank-online", "query-modules/cpp/periodic", + "query-modules/cpp/refactor", "query-modules/python/set-cover", "query-modules/python/temporal", "query-modules/python/temporal-graph-networks",