From 86ce33f7ea8e32e0a7fef232824ee7a694d68387 Mon Sep 17 00:00:00 2001 From: Marko Budiselic Date: Thu, 16 Jul 2026 20:35:48 +0200 Subject: [PATCH 01/19] Add Memgraph v3.13.0 --- pages/release-notes.mdx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 0cf175471..e12028e7e 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -46,6 +46,14 @@ guide. ## πŸš€ Latest release +### Memgraph v3.13.0 - September 9th, 2026 + +### Lab v3.13.0 - September 9th, 2026 + + + +## Previous releases + ### Memgraph v3.12.0 - July 15th, 2026 {

⚠️ Breaking changes

} @@ -196,8 +204,6 @@ guide. -## Previous releases - ### Memgraph v3.11.0 - June 17th, 2026 {

⚠️ Breaking changes

} From c87ada73ab2125473b908e8a0a1d0990167d98b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:06:05 +0200 Subject: [PATCH 02/19] docs: support RANGE keyword in CREATE INDEX ... FOR syntax (#1702) * docs: document RANGE keyword in CREATE INDEX ... FOR syntax * docs: drop unnecessary note about RANGE with native ON syntax --- pages/querying/differences-in-cypher-implementations.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pages/querying/differences-in-cypher-implementations.mdx b/pages/querying/differences-in-cypher-implementations.mdx index 687f1a700..48df4b6c1 100644 --- a/pages/querying/differences-in-cypher-implementations.mdx +++ b/pages/querying/differences-in-cypher-implementations.mdx @@ -34,6 +34,15 @@ CREATE INDEX FOR (n:Person) ON (n.age, n.country); CREATE INDEX FOR ()-[r:KNOWS]-() ON (r.since); ``` +The optional `RANGE` keyword is also accepted in the `FOR ... ON` syntax, for both nodes and relationships: + +```cypher +CREATE RANGE INDEX FOR (n:Person) ON (n.surname); +CREATE RANGE INDEX FOR ()-[r:KNOWS]-() ON (r.since); +``` + +Neo4j's `RANGE` index is its general-purpose ordered property index, used for equality, range and prefix lookups β€” the same role Memgraph's label-property and edge-type property indexes already fill. `RANGE` is therefore accepted as a synonym: it maps onto the existing index with no new index type and no change in behavior. + The native Memgraph syntax remains supported as well: ```cypher From e80149619c687177eb947d1d03167cb517cec804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:20:26 +0200 Subject: [PATCH 03/19] docs: add map.get and map.merge_list; align map functions on null/coercion behavior (#1696) * docs: document map.get and map.merge_list, and map null/coercion behavior * docs: note node/relationship coercion for map.remove_key, remove_keys, flatten * docs: mark map.set_key value argument as nullable --- .../available-algorithms/map.mdx | 103 ++++++++++++++++-- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/pages/advanced-algorithms/available-algorithms/map.mdx b/pages/advanced-algorithms/available-algorithms/map.mdx index 320171d59..659613b83 100644 --- a/pages/advanced-algorithms/available-algorithms/map.mdx +++ b/pages/advanced-algorithms/available-algorithms/map.mdx @@ -40,7 +40,7 @@ from any inner maps that are part of the input map. {

Input:

} -- `map: Map` ➑ The map from which the key will be removed. +- `map: Map` ➑ The map from which the key will be removed (a node or relationship may be passed; its properties are used). - `key: string` ➑ The key to be removed from the map. - `config: Map default = {recursive: false}` ➑ The config map which supports the `recursive` option. The option `recursive` is `false` by default, and should be @@ -94,7 +94,7 @@ This function is equivalent to **apoc.map.removeKeys**. {

Input:

} -- `map: Map[Any]` ➑ The input map. +- `map: Map[Any]` ➑ The input map (a node or relationship may be passed; its properties are used). - `keys: List[string]` ➑ A list of keys that will be removed. - `config: Map default = {recursive: false}` ➑ A config map which supports the `recursive` option. The `recursive` option is `false` by default, and should be @@ -181,8 +181,8 @@ This function is equivalent to **apoc.map.merge**. {

Input:

} -- `first: mgp.Nullable[Map]` ➑ A map containing key-value pairs that need to be merged with another map. -- `second: mgp.Nullable[Map]` ➑ The second map containing key-value pairs that need to be merged with the key-values from the first map. +- `map1: mgp.Nullable[Map]` ➑ The first map to merge (a node or relationship may be passed; its properties are used). +- `map2: mgp.Nullable[Map]` ➑ The second map to merge. On a key conflict, its value takes precedence. {

Output:

} @@ -204,13 +204,47 @@ RETURN map.merge({a: "b", c: "d"}, {e: "f", g: "h"}) AS merged; +----------------------------------------+ ``` +### `merge_list()` + +Merges a list of maps into a single map. Keys are merged left to right, so when +the same key appears in more than one map, the value from the last map wins. An +empty list yields an empty map. + + +This function is equivalent to **apoc.map.mergeList**. + + +{

Input:

} + +- `maps: List[Map]` ➑ The maps to merge (each element may be a node or relationship, whose properties are used). + +{

Output:

} + +- `Map` ➑ The merged map. + +{

Usage:

} + +The following query merges a list of maps: + +```cypher +RETURN map.merge_list([{a: 1}, {a: 2, b: 3}]) AS merged; +``` + +```plaintext ++----------------------------------------+ +| merged | ++----------------------------------------+ +| {a: 2, b: 3} | ++----------------------------------------+ +``` + ### `flatten()` The procedure flattens nested items in the input map. {

Input:

} -- `map: Map[Any]` ➑ The input map that needs to be modified. +- `map: Map[Any]` ➑ The input map that needs to be modified (a node or relationship may be passed; its properties are used). - `delimiter: string (default = ".")` ➑ The delimiter used for flattening. {

Output:

} @@ -270,8 +304,12 @@ RETURN map.from_lists(["key","key2"],[1,2]) AS result; ### `from_values()` Returns a map from the given list of values. The list has the format: `[key1, -value1, key2, value2]`. If the key is not convertible to a string, the function -throws `ValueException`. +value1, key2, value2]`. Keys are converted to strings; a pair whose key is +`null` is skipped (its value is ignored). + + +This function is equivalent to **apoc.map.fromValues**. + {

Input:

} @@ -300,13 +338,18 @@ RETURN map.from_values(["day", "sunny", 5, 6]) AS map; ### `set_key()` Updates the value at the position `key` in a map. If the key doesn't exist, -the function will insert it. +the function will insert it. A `null` map is treated as empty and a `null` key +is a no-op (the map is returned unchanged). + + +This function is equivalent to **apoc.map.setKey**. + {

Input:

} -- `map: Map` ➑ The map that will be modified. -- `key: string` ➑ The key of a certain key-value pair that needs to have a new value. -- `value: any` ➑ The new value of a certain key-value pair. +- `map: mgp.Nullable[Map]` ➑ The map that will be modified (a node or relationship may be passed; its properties are used). +- `key: mgp.Nullable[string]` ➑ The key to add or update; a `null` key leaves the map unchanged. +- `value: mgp.Nullable[Any]` ➑ The new value of the key-value pair. {

Output:

} @@ -328,6 +371,44 @@ RETURN map.set_key({name:"Ivan",country:"Croatia"}, "name", "Matija") AS map; +-------------------------------------------+ ``` +### `get()` + +Returns the value stored under `key` in the map. If the key is absent, the +function returns `value` when it is non-null; otherwise it throws when `fail` is +`true` (the default) or returns `null` when `fail` is `false`. An existing key +always wins, even when its stored value is `null`. + + +This function is equivalent to **apoc.map.get**. + + +{

Input:

} + +- `map: Map` ➑ The map to look up (a node or relationship may be passed; its properties are used). +- `key: string` ➑ The key to look up. +- `value: any (default = null)` ➑ The value returned when the key is absent. +- `fail: boolean (default = true)` ➑ When `true`, throws if the key is absent and `value` is null; when `false`, returns `null` instead. + +{

Output:

} + +- `any` ➑ The value at `key`, the fallback `value`, or `null`. + +{

Usage:

} + +The following query returns the fallback value because the key is absent: + +```cypher +RETURN map.get({name: "Ivan"}, "country", "unknown", false) AS value; +``` + +```plaintext ++----------------------------------------+ +| value | ++----------------------------------------+ +| "unknown" | ++----------------------------------------+ +``` + ## Procedures ### `from_nodes()` From 24e8e8a27b218a8480ea27c054ef5a0b1edaa96b Mon Sep 17 00:00:00 2001 From: Dr Matt James Date: Tue, 28 Jul 2026 15:20:57 +0100 Subject: [PATCH 04/19] refactor: build and package MAGE from the unified CMake tree (#1700) * refactor: build and package MAGE from the unified CMake tree * update page --- pages/advanced-algorithms.mdx | 2 +- .../available-algorithms.mdx | 2 +- .../available-algorithms/algo.mdx | 2 +- .../betweenness_centrality.mdx | 2 +- .../betweenness_centrality_online.mdx | 2 +- .../biconnected_components.mdx | 2 +- .../bipartite_matching.mdx | 2 +- .../available-algorithms/bridges.mdx | 2 +- .../available-algorithms/collections.mdx | 2 +- .../community_detection.mdx | 2 +- .../available-algorithms/create.mdx | 2 +- .../available-algorithms/cross_database.mdx | 2 +- .../available-algorithms/csv_utils.mdx | 2 +- .../available-algorithms/cugraph.mdx | 2 +- .../available-algorithms/cycles.mdx | 2 +- .../degree_centrality.mdx | 2 +- .../distance_calculator.mdx | 2 +- .../available-algorithms/do.mdx | 2 +- .../elasticsearch_synchronization.mdx | 2 +- .../available-algorithms/embeddings.mdx | 2 +- .../available-algorithms/export_util.mdx | 2 +- .../available-algorithms/gnn.mdx | 2 +- .../gnn_link_prediction.mdx | 4 +- .../gnn_node_classification.mdx | 2 +- .../available-algorithms/graph_coloring.mdx | 2 +- .../available-algorithms/graph_util.mdx | 2 +- .../available-algorithms/igraphalg.mdx | 2 +- .../available-algorithms/import_util.mdx | 2 +- .../available-algorithms/json_util.mdx | 2 +- .../available-algorithms/katz_centrality.mdx | 2 +- .../kmeans_clustering.mdx | 2 +- .../available-algorithms/knn.mdx | 2 +- .../available-algorithms/label.mdx | 2 +- .../leiden_community_detection.mdx | 2 +- .../available-algorithms/llm_util.mdx | 2 +- .../available-algorithms/map.mdx | 2 +- .../available-algorithms/math.mdx | 2 +- .../available-algorithms/max_flow.mdx | 2 +- .../available-algorithms/merge.mdx | 2 +- .../available-algorithms/meta.mdx | 2 +- .../available-algorithms/meta_util.mdx | 2 +- .../available-algorithms/neighbors.mdx | 2 +- .../available-algorithms/node.mdx | 2 +- .../available-algorithms/node2vec.mdx | 2 +- .../available-algorithms/node_similarity.mdx | 2 +- .../available-algorithms/nodes.mdx | 2 +- .../available-algorithms/pagerank.mdx | 2 +- .../available-algorithms/path.mdx | 2 +- .../available-algorithms/periodic.mdx | 2 +- .../available-algorithms/refactor.mdx | 2 +- .../available-algorithms/set_cover.mdx | 2 +- .../available-algorithms/set_property.mdx | 2 +- .../available-algorithms/temporal.mdx | 2 +- .../available-algorithms/text.mdx | 2 +- .../available-algorithms/tgn.mdx | 2 +- .../available-algorithms/tsp.mdx | 2 +- .../available-algorithms/union_find.mdx | 2 +- .../available-algorithms/util_module.mdx | 2 +- .../available-algorithms/uuid_generator.mdx | 2 +- .../available-algorithms/vrp.mdx | 2 +- .../weakly_connected_components.mdx | 2 +- .../available-algorithms/xml_module.mdx | 2 +- pages/advanced-algorithms/install-mage.mdx | 117 +++++++---------- pages/custom-query-modules.mdx | 122 ++++++------------ pages/custom-query-modules/contributing.mdx | 2 +- .../custom-query-modules/cpp/cpp-example.mdx | 2 +- pages/getting-started/packaging-memgraph.mdx | 42 +++++- 67 files changed, 185 insertions(+), 226 deletions(-) diff --git a/pages/advanced-algorithms.mdx b/pages/advanced-algorithms.mdx index 34abb89c3..d755ec38e 100644 --- a/pages/advanced-algorithms.mdx +++ b/pages/advanced-algorithms.mdx @@ -30,7 +30,7 @@ Dynamic graph algorithms are a part of [Memgraph Enterprise license](/database-management/enabling-memgraph-enterprise). For more algorithms, check the **Memgraph Advanced Graph Extensions** (**MAGE**) -library. It is part of the [Memgraph GitHub repository](https://github.com/memgraph/memgraph/tree/master/mage) +library. It is part of the [Memgraph GitHub repository](https://github.com/memgraph/memgraph/tree/master/src/mage) that contains [graph algorithms](/advanced-algorithms/available-algorithms) written by the team behind Memgraph and its users in the form of query modules. The project aims to give everyone the tools they need to tackle the most diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index 749aa1ea2..b6e6dc234 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -111,7 +111,7 @@ If you want to know more and learn how this affects you, read our [announcement] | [nodes](/advanced-algorithms/available-algorithms/nodes) | C++ | A module that provides a comprehensive toolkit for managing multiple graph nodes, enabling linking, updating, type deduction and more. | | [periodic](/advanced-algorithms/available-algorithms/periodic) | C++ | A module containing procedures for periodically running difficult and/or memory/time consuming queries. | | [refactor](/advanced-algorithms/available-algorithms/refactor) | C++ | The refactor module provides utilities for changing nodes and relationships. | -| [rust_example](https://github.com/memgraph/memgraph/tree/master/mage/rust/rsmgp-example) | Rust | Example of a basic module with input parameters forwarding, made in Rust. | +| [rust_example](https://github.com/memgraph/memgraph/tree/master/src/mage/rust/rsmgp-example) | Rust | Example of a basic module with input parameters forwarding, made in Rust. | | [set_property](/advanced-algorithms/available-algorithms/set_property) | C++ | A module for dynamical access and editing of node and relationship properties. | | [temporal](/advanced-algorithms/available-algorithms/temporal) | Python | A module that provides functions to handle temporal (time-related) operations and offers extended capabilities compared to the date module. | | [text](/advanced-algorithms/available-algorithms/text) | C++ | The `text` module offers a toolkit for manipulating strings. | diff --git a/pages/advanced-algorithms/available-algorithms/algo.mdx b/pages/advanced-algorithms/available-algorithms/algo.mdx index 16d6a13ec..c3d418b04 100644 --- a/pages/advanced-algorithms/available-algorithms/algo.mdx +++ b/pages/advanced-algorithms/available-algorithms/algo.mdx @@ -16,7 +16,7 @@ enabling users to perform complex graph-based operations and computations, such } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/algo_module/algo_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/algo_module/algo_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/betweenness_centrality.mdx b/pages/advanced-algorithms/available-algorithms/betweenness_centrality.mdx index 43f92557a..7383b0062 100644 --- a/pages/advanced-algorithms/available-algorithms/betweenness_centrality.mdx +++ b/pages/advanced-algorithms/available-algorithms/betweenness_centrality.mdx @@ -28,7 +28,7 @@ Centrality"](http://www.uvm.edu/pdodds/research/papers/others/2001/brandes2001a. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/betweenness_centrality_module/betweenness_centrality_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/betweenness_centrality_module/betweenness_centrality_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/betweenness_centrality_online.mdx b/pages/advanced-algorithms/available-algorithms/betweenness_centrality_online.mdx index 3d7ed9507..09f0b3654 100644 --- a/pages/advanced-algorithms/available-algorithms/betweenness_centrality_online.mdx +++ b/pages/advanced-algorithms/available-algorithms/betweenness_centrality_online.mdx @@ -48,7 +48,7 @@ reflective of real-time changes. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/betweenness_centrality_module/betweenness_centrality_online_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/betweenness_centrality_module/betweenness_centrality_online_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/biconnected_components.mdx b/pages/advanced-algorithms/available-algorithms/biconnected_components.mdx index a50fd6915..2144fd3a6 100644 --- a/pages/advanced-algorithms/available-algorithms/biconnected_components.mdx +++ b/pages/advanced-algorithms/available-algorithms/biconnected_components.mdx @@ -21,7 +21,7 @@ The algorithm works by finding articulation points, and then traversing from the } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/biconnected_components_module/biconnected_components_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/biconnected_components_module/biconnected_components_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/bipartite_matching.mdx b/pages/advanced-algorithms/available-algorithms/bipartite_matching.mdx index 9532fee59..84ed70d80 100644 --- a/pages/advanced-algorithms/available-algorithms/bipartite_matching.mdx +++ b/pages/advanced-algorithms/available-algorithms/bipartite_matching.mdx @@ -23,7 +23,7 @@ set of edges (relationships). } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/bipartite_matching_module/bipartite_matching_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/bipartite_matching_module/bipartite_matching_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/bridges.mdx b/pages/advanced-algorithms/available-algorithms/bridges.mdx index 1bf17867e..459ab30fb 100644 --- a/pages/advanced-algorithms/available-algorithms/bridges.mdx +++ b/pages/advanced-algorithms/available-algorithms/bridges.mdx @@ -20,7 +20,7 @@ valuable to detect it on time. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/bridges_module/bridges_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/bridges_module/bridges_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/collections.mdx b/pages/advanced-algorithms/available-algorithms/collections.mdx index 339874be6..4c6c7920c 100644 --- a/pages/advanced-algorithms/available-algorithms/collections.mdx +++ b/pages/advanced-algorithms/available-algorithms/collections.mdx @@ -20,7 +20,7 @@ using the `CALL` subclause. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/collections_module/collections_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/collections_module/collections_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/community_detection.mdx b/pages/advanced-algorithms/available-algorithms/community_detection.mdx index 51144ea3b..ed6cdc5c2 100644 --- a/pages/advanced-algorithms/available-algorithms/community_detection.mdx +++ b/pages/advanced-algorithms/available-algorithms/community_detection.mdx @@ -32,7 +32,7 @@ preserving communities. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/community_detection_module/community_detection_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/community_detection_module/community_detection_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/create.mdx b/pages/advanced-algorithms/available-algorithms/create.mdx index cebc0f5aa..31f4f4bef 100644 --- a/pages/advanced-algorithms/available-algorithms/create.mdx +++ b/pages/advanced-algorithms/available-algorithms/create.mdx @@ -17,7 +17,7 @@ model, manipulate, and query complex graph data. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/create_module/create_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/create_module/create_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/cross_database.mdx b/pages/advanced-algorithms/available-algorithms/cross_database.mdx index 1856d6704..00f462f3a 100644 --- a/pages/advanced-algorithms/available-algorithms/cross_database.mdx +++ b/pages/advanced-algorithms/available-algorithms/cross_database.mdx @@ -24,7 +24,7 @@ procedure has been replaced by the more general [`cross_database.bolt()`](#bolt) } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/cross_database.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/cross_database.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/csv_utils.mdx b/pages/advanced-algorithms/available-algorithms/csv_utils.mdx index 1c1e91f0c..03afe001c 100644 --- a/pages/advanced-algorithms/available-algorithms/csv_utils.mdx +++ b/pages/advanced-algorithms/available-algorithms/csv_utils.mdx @@ -16,7 +16,7 @@ It allows users to create and delete CSV files directly from the database enviro } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/csv_utils_module/csv_utils_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/csv_utils_module/csv_utils_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/cugraph.mdx b/pages/advanced-algorithms/available-algorithms/cugraph.mdx index c63ba24ac..05a324041 100644 --- a/pages/advanced-algorithms/available-algorithms/cugraph.mdx +++ b/pages/advanced-algorithms/available-algorithms/cugraph.mdx @@ -23,7 +23,7 @@ wrappers for most of the algorithms present in the } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/cugraph_module" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/cugraph_module" /> diff --git a/pages/advanced-algorithms/available-algorithms/cycles.mdx b/pages/advanced-algorithms/available-algorithms/cycles.mdx index 6da8bbe66..b853583dc 100644 --- a/pages/advanced-algorithms/available-algorithms/cycles.mdx +++ b/pages/advanced-algorithms/available-algorithms/cycles.mdx @@ -26,7 +26,7 @@ set of nodes (vertices) of the given graph. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/cycles_module/cycles_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/cycles_module/cycles_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/degree_centrality.mdx b/pages/advanced-algorithms/available-algorithms/degree_centrality.mdx index 308b0252d..bbc62f4ff 100644 --- a/pages/advanced-algorithms/available-algorithms/degree_centrality.mdx +++ b/pages/advanced-algorithms/available-algorithms/degree_centrality.mdx @@ -25,7 +25,7 @@ a_{i,k}$ or in matrix form: $y = A 1$. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/degree_centrality_module/degree_centrality_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/degree_centrality_module/degree_centrality_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/distance_calculator.mdx b/pages/advanced-algorithms/available-algorithms/distance_calculator.mdx index 3e086203c..22ee28cd6 100644 --- a/pages/advanced-algorithms/available-algorithms/distance_calculator.mdx +++ b/pages/advanced-algorithms/available-algorithms/distance_calculator.mdx @@ -24,7 +24,7 @@ this: } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/distance_calculator/distance_calculator.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/distance_calculator/distance_calculator.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/do.mdx b/pages/advanced-algorithms/available-algorithms/do.mdx index e3ec392e8..cad9f2925 100644 --- a/pages/advanced-algorithms/available-algorithms/do.mdx +++ b/pages/advanced-algorithms/available-algorithms/do.mdx @@ -18,7 +18,7 @@ that will control query execution. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/do_module/do_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/do_module/do_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/elasticsearch_synchronization.mdx b/pages/advanced-algorithms/available-algorithms/elasticsearch_synchronization.mdx index f1d6b68a4..a72fd2927 100644 --- a/pages/advanced-algorithms/available-algorithms/elasticsearch_synchronization.mdx +++ b/pages/advanced-algorithms/available-algorithms/elasticsearch_synchronization.mdx @@ -49,7 +49,7 @@ create new ones with custom schema**. Indexing can be performed in two ways: } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/elastic_search_serialization.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/elastic_search_serialization.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/embeddings.mdx b/pages/advanced-algorithms/available-algorithms/embeddings.mdx index c7360ad43..b1034c9c7 100644 --- a/pages/advanced-algorithms/available-algorithms/embeddings.mdx +++ b/pages/advanced-algorithms/available-algorithms/embeddings.mdx @@ -27,7 +27,7 @@ credentials. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/embeddings.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/embeddings.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/export_util.mdx b/pages/advanced-algorithms/available-algorithms/export_util.mdx index 9b9feee57..961be5b4d 100644 --- a/pages/advanced-algorithms/available-algorithms/export_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/export_util.mdx @@ -24,7 +24,7 @@ Currently, this module supports: } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/export_util.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/export_util.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/gnn.mdx b/pages/advanced-algorithms/available-algorithms/gnn.mdx index e1341f698..5278db274 100644 --- a/pages/advanced-algorithms/available-algorithms/gnn.mdx +++ b/pages/advanced-algorithms/available-algorithms/gnn.mdx @@ -29,7 +29,7 @@ Typical workflow: } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/gnn.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/gnn.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/gnn_link_prediction.mdx b/pages/advanced-algorithms/available-algorithms/gnn_link_prediction.mdx index 07c29dcb5..dc40f9907 100644 --- a/pages/advanced-algorithms/available-algorithms/gnn_link_prediction.mdx +++ b/pages/advanced-algorithms/available-algorithms/gnn_link_prediction.mdx @@ -32,7 +32,7 @@ representations by aggregating the representations of node neighbors and their representation from the previous iteration. Such properties make **graph neural networks** a great tool for various problems we in Memgraph encounter. If your graph is evolving in time, check [TGN -model](https://github.com/memgraph/memgraph/blob/master/mage/python/tgn.py) that Memgraph +model](https://github.com/memgraph/memgraph/blob/master/src/mage/python/tgn.py) that Memgraph engineers have already developed. In this includes the following features: @@ -92,7 +92,7 @@ For the underlying **GNN** training Memgraph uses the [DGL library](https://gith } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/link_prediction.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/link_prediction.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/gnn_node_classification.mdx b/pages/advanced-algorithms/available-algorithms/gnn_node_classification.mdx index f1e7fcb8d..9af741596 100644 --- a/pages/advanced-algorithms/available-algorithms/gnn_node_classification.mdx +++ b/pages/advanced-algorithms/available-algorithms/gnn_node_classification.mdx @@ -45,7 +45,7 @@ useful. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/node_classification.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/node_classification.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/graph_coloring.mdx b/pages/advanced-algorithms/available-algorithms/graph_coloring.mdx index 0888f2c51..faff998ef 100644 --- a/pages/advanced-algorithms/available-algorithms/graph_coloring.mdx +++ b/pages/advanced-algorithms/available-algorithms/graph_coloring.mdx @@ -40,7 +40,7 @@ converging to local minimums too early. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/graph_coloring.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/graph_coloring.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/graph_util.mdx b/pages/advanced-algorithms/available-algorithms/graph_util.mdx index 43cc207a4..5f12ffddb 100644 --- a/pages/advanced-algorithms/available-algorithms/graph_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/graph_util.mdx @@ -18,7 +18,7 @@ manipulation tools to accelerate development. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/graph_util_module/graph_util_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/graph_util_module/graph_util_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/igraphalg.mdx b/pages/advanced-algorithms/available-algorithms/igraphalg.mdx index 47b62a7ec..7a5d734fa 100644 --- a/pages/advanced-algorithms/available-algorithms/igraphalg.mdx +++ b/pages/advanced-algorithms/available-algorithms/igraphalg.mdx @@ -18,7 +18,7 @@ stream the native database graph directly, significantly lowering memory usage. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/igraphalg.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/igraphalg.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/import_util.mdx b/pages/advanced-algorithms/available-algorithms/import_util.mdx index 04bcc81d5..b6f16e1a4 100644 --- a/pages/advanced-algorithms/available-algorithms/import_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/import_util.mdx @@ -18,7 +18,7 @@ supports the import of JSON and graphML file formats. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/import_util.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/import_util.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/json_util.mdx b/pages/advanced-algorithms/available-algorithms/json_util.mdx index 4a01a1600..2b77d21de 100644 --- a/pages/advanced-algorithms/available-algorithms/json_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/json_util.mdx @@ -18,7 +18,7 @@ and if it is a map, the module loads it as a single value. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/json_util.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/json_util.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/katz_centrality.mdx b/pages/advanced-algorithms/available-algorithms/katz_centrality.mdx index fc9174b61..c336b2804 100644 --- a/pages/advanced-algorithms/available-algorithms/katz_centrality.mdx +++ b/pages/advanced-algorithms/available-algorithms/katz_centrality.mdx @@ -38,7 +38,7 @@ resulting centralities will be correct. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/katz_centrality_module/katz_centrality_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/katz_centrality_module/katz_centrality_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/kmeans_clustering.mdx b/pages/advanced-algorithms/available-algorithms/kmeans_clustering.mdx index 25b7abd4e..ca50792d7 100644 --- a/pages/advanced-algorithms/available-algorithms/kmeans_clustering.mdx +++ b/pages/advanced-algorithms/available-algorithms/kmeans_clustering.mdx @@ -18,7 +18,7 @@ sum-of-squares. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/kmeans.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/kmeans.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/knn.mdx b/pages/advanced-algorithms/available-algorithms/knn.mdx index b71dde9a8..9c228f1a4 100644 --- a/pages/advanced-algorithms/available-algorithms/knn.mdx +++ b/pages/advanced-algorithms/available-algorithms/knn.mdx @@ -23,7 +23,7 @@ finding nodes with similar embeddings, features, or other vector-based propertie } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/knn_module/knn_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/knn_module/knn_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/label.mdx b/pages/advanced-algorithms/available-algorithms/label.mdx index 8da9d0b8a..73327f563 100644 --- a/pages/advanced-algorithms/available-algorithms/label.mdx +++ b/pages/advanced-algorithms/available-algorithms/label.mdx @@ -17,7 +17,7 @@ to check the existence of a label within the node. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/label_module/label_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/label_module/label_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/leiden_community_detection.mdx b/pages/advanced-algorithms/available-algorithms/leiden_community_detection.mdx index 3fbd31a39..bc6655d3d 100644 --- a/pages/advanced-algorithms/available-algorithms/leiden_community_detection.mdx +++ b/pages/advanced-algorithms/available-algorithms/leiden_community_detection.mdx @@ -32,7 +32,7 @@ space complexity if $\mathcal{O}(VE)$ for $V$ nodes and $E$ edges. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/leiden_community_detection_module/leiden_community_detection_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/leiden_community_detection_module/leiden_community_detection_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/llm_util.mdx b/pages/advanced-algorithms/available-algorithms/llm_util.mdx index fb3add1bc..5fdf93d5e 100644 --- a/pages/advanced-algorithms/available-algorithms/llm_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/llm_util.mdx @@ -26,7 +26,7 @@ developing applications powered by language models. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/llm_util.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/llm_util.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/map.mdx b/pages/advanced-algorithms/available-algorithms/map.mdx index 659613b83..e3cfc5995 100644 --- a/pages/advanced-algorithms/available-algorithms/map.mdx +++ b/pages/advanced-algorithms/available-algorithms/map.mdx @@ -17,7 +17,7 @@ context. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/map_module/map_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/map_module/map_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/math.mdx b/pages/advanced-algorithms/available-algorithms/math.mdx index 85e0ff78b..2ac659a11 100644 --- a/pages/advanced-algorithms/available-algorithms/math.mdx +++ b/pages/advanced-algorithms/available-algorithms/math.mdx @@ -15,7 +15,7 @@ The `math` module provides essential mathematical operations for precise numeric } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/math_module/math_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/math_module/math_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/max_flow.mdx b/pages/advanced-algorithms/available-algorithms/max_flow.mdx index e3595fbd9..26870030d 100644 --- a/pages/advanced-algorithms/available-algorithms/max_flow.mdx +++ b/pages/advanced-algorithms/available-algorithms/max_flow.mdx @@ -37,7 +37,7 @@ returning max flow value is 0. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/max_flow.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/max_flow.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/merge.mdx b/pages/advanced-algorithms/available-algorithms/merge.mdx index 7abdc1554..df5cad88b 100644 --- a/pages/advanced-algorithms/available-algorithms/merge.mdx +++ b/pages/advanced-algorithms/available-algorithms/merge.mdx @@ -16,7 +16,7 @@ It ensures precision and coherence in managing interconnected data structures. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/merge_module/merge_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/merge_module/merge_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/meta.mdx b/pages/advanced-algorithms/available-algorithms/meta.mdx index 743832583..003535ed0 100644 --- a/pages/advanced-algorithms/available-algorithms/meta.mdx +++ b/pages/advanced-algorithms/available-algorithms/meta.mdx @@ -21,7 +21,7 @@ The **meta** module provides a set of procedures for generating metadata about t } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/meta_module/meta_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/meta_module/meta_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/meta_util.mdx b/pages/advanced-algorithms/available-algorithms/meta_util.mdx index 69574b053..bb1cc714a 100644 --- a/pages/advanced-algorithms/available-algorithms/meta_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/meta_util.mdx @@ -16,7 +16,7 @@ A module that contains procedures describing graphs on a meta-level. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/meta_util.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/meta_util.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/neighbors.mdx b/pages/advanced-algorithms/available-algorithms/neighbors.mdx index ce0cd70a0..585b059a5 100644 --- a/pages/advanced-algorithms/available-algorithms/neighbors.mdx +++ b/pages/advanced-algorithms/available-algorithms/neighbors.mdx @@ -18,7 +18,7 @@ network structure and connectivity. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/neighbors_module/neighbors_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/neighbors_module/neighbors_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/node.mdx b/pages/advanced-algorithms/available-algorithms/node.mdx index 5ed38df04..ba71b7e57 100644 --- a/pages/advanced-algorithms/available-algorithms/node.mdx +++ b/pages/advanced-algorithms/available-algorithms/node.mdx @@ -15,7 +15,7 @@ The `node` module provides a comprehensive toolkit for managing graph nodes, ena } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/node_module/node_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/node_module/node_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/node2vec.mdx b/pages/advanced-algorithms/available-algorithms/node2vec.mdx index 01b6a6e24..a58a9c685 100644 --- a/pages/advanced-algorithms/available-algorithms/node2vec.mdx +++ b/pages/advanced-algorithms/available-algorithms/node2vec.mdx @@ -66,7 +66,7 @@ are correct in approximately 75% cases). } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/node2vec.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/node2vec.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/node_similarity.mdx b/pages/advanced-algorithms/available-algorithms/node_similarity.mdx index b5696e96e..57589271d 100644 --- a/pages/advanced-algorithms/available-algorithms/node_similarity.mdx +++ b/pages/advanced-algorithms/available-algorithms/node_similarity.mdx @@ -52,7 +52,7 @@ takes into account pairwise similarities between two set of nodes. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/node_similarity_module/node_similarity_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/node_similarity_module/node_similarity_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/nodes.mdx b/pages/advanced-algorithms/available-algorithms/nodes.mdx index cfd3c5103..22cdff1ef 100644 --- a/pages/advanced-algorithms/available-algorithms/nodes.mdx +++ b/pages/advanced-algorithms/available-algorithms/nodes.mdx @@ -15,7 +15,7 @@ The `nodes` module provides a comprehensive toolkit for managing multiple graph } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/nodes_module/nodes_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/nodes_module/nodes_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/pagerank.mdx b/pages/advanced-algorithms/available-algorithms/pagerank.mdx index 045b6509a..8ceabb59d 100644 --- a/pages/advanced-algorithms/available-algorithms/pagerank.mdx +++ b/pages/advanced-algorithms/available-algorithms/pagerank.mdx @@ -42,7 +42,7 @@ PageRank implementation. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/pagerank_module/pagerank_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/pagerank_module/pagerank_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/path.mdx b/pages/advanced-algorithms/available-algorithms/path.mdx index 6ae75ce9c..a34654d22 100644 --- a/pages/advanced-algorithms/available-algorithms/path.mdx +++ b/pages/advanced-algorithms/available-algorithms/path.mdx @@ -19,7 +19,7 @@ various other path-oriented operations. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/path_module/path_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/path_module/path_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/periodic.mdx b/pages/advanced-algorithms/available-algorithms/periodic.mdx index e480283c6..87f241e75 100644 --- a/pages/advanced-algorithms/available-algorithms/periodic.mdx +++ b/pages/advanced-algorithms/available-algorithms/periodic.mdx @@ -44,7 +44,7 @@ procedure, the already committed batches cannot be rolled back. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/periodic_module/periodic.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/periodic_module/periodic.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/refactor.mdx b/pages/advanced-algorithms/available-algorithms/refactor.mdx index 987f09a10..36d5c054f 100644 --- a/pages/advanced-algorithms/available-algorithms/refactor.mdx +++ b/pages/advanced-algorithms/available-algorithms/refactor.mdx @@ -15,7 +15,7 @@ The `refactor` module provides utilities for changing nodes and relationships. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/refactor_module/refactor_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/refactor_module/refactor_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/set_cover.mdx b/pages/advanced-algorithms/available-algorithms/set_cover.mdx index d8dd6023e..1d9d7c45a 100644 --- a/pages/advanced-algorithms/available-algorithms/set_cover.mdx +++ b/pages/advanced-algorithms/available-algorithms/set_cover.mdx @@ -22,7 +22,7 @@ a constraint programming solver. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/set_cover.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/set_cover.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/set_property.mdx b/pages/advanced-algorithms/available-algorithms/set_property.mdx index 6c991ef9b..6786c7d2c 100644 --- a/pages/advanced-algorithms/available-algorithms/set_property.mdx +++ b/pages/advanced-algorithms/available-algorithms/set_property.mdx @@ -16,7 +16,7 @@ procedures included in it involve copying properties from one entity to another. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/set_property_module/set_property_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/set_property_module/set_property_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/temporal.mdx b/pages/advanced-algorithms/available-algorithms/temporal.mdx index 0a7b62a02..fb26ff16f 100644 --- a/pages/advanced-algorithms/available-algorithms/temporal.mdx +++ b/pages/advanced-algorithms/available-algorithms/temporal.mdx @@ -17,7 +17,7 @@ functions. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/temporal.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/temporal.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/text.mdx b/pages/advanced-algorithms/available-algorithms/text.mdx index 2c738184f..2966458bb 100644 --- a/pages/advanced-algorithms/available-algorithms/text.mdx +++ b/pages/advanced-algorithms/available-algorithms/text.mdx @@ -20,7 +20,7 @@ The `text` module offers a toolkit for manipulating strings. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/text_module/text_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/text_module/text_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/tgn.mdx b/pages/advanced-algorithms/available-algorithms/tgn.mdx index 244bcc986..3574d2e1e 100644 --- a/pages/advanced-algorithms/available-algorithms/tgn.mdx +++ b/pages/advanced-algorithms/available-algorithms/tgn.mdx @@ -138,7 +138,7 @@ new edges are forwarded to the **TGN** and so on. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/tgn.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/tgn.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/tsp.mdx b/pages/advanced-algorithms/available-algorithms/tsp.mdx index 8b8cc9595..ac791253a 100644 --- a/pages/advanced-algorithms/available-algorithms/tsp.mdx +++ b/pages/advanced-algorithms/available-algorithms/tsp.mdx @@ -29,7 +29,7 @@ needs to have its *lat* and *lng* property. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/tsp.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/tsp.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/union_find.mdx b/pages/advanced-algorithms/available-algorithms/union_find.mdx index bf4a421f6..ab46a3c34 100644 --- a/pages/advanced-algorithms/available-algorithms/union_find.mdx +++ b/pages/advanced-algorithms/available-algorithms/union_find.mdx @@ -27,7 +27,7 @@ Algorithms](https://dl.acm.org/doi/10.1145/62.2160)" and presented with } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/union_find.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/union_find.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/util_module.mdx b/pages/advanced-algorithms/available-algorithms/util_module.mdx index 4349dc27c..c75b605ca 100644 --- a/pages/advanced-algorithms/available-algorithms/util_module.mdx +++ b/pages/advanced-algorithms/available-algorithms/util_module.mdx @@ -17,7 +17,7 @@ for streamlining a variety of tasks related to database operations. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/util_module/util_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/util_module/util_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/uuid_generator.mdx b/pages/advanced-algorithms/available-algorithms/uuid_generator.mdx index 464d1b3ed..f4d378305 100644 --- a/pages/advanced-algorithms/available-algorithms/uuid_generator.mdx +++ b/pages/advanced-algorithms/available-algorithms/uuid_generator.mdx @@ -19,7 +19,7 @@ installed by running `sudo apt-get install uuid-dev`. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/uuid_module/uuid_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/uuid_module/uuid_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/vrp.mdx b/pages/advanced-algorithms/available-algorithms/vrp.mdx index 419b41e14..22791a88b 100644 --- a/pages/advanced-algorithms/available-algorithms/vrp.mdx +++ b/pages/advanced-algorithms/available-algorithms/vrp.mdx @@ -31,7 +31,7 @@ have its *lat* and *lng* property. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/vrp.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/vrp.py" /> diff --git a/pages/advanced-algorithms/available-algorithms/weakly_connected_components.mdx b/pages/advanced-algorithms/available-algorithms/weakly_connected_components.mdx index bbf987914..d2e949d80 100644 --- a/pages/advanced-algorithms/available-algorithms/weakly_connected_components.mdx +++ b/pages/advanced-algorithms/available-algorithms/weakly_connected_components.mdx @@ -20,7 +20,7 @@ there is no edge that connects nodes from separate components. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/connectivity_module/connectivity_module.cpp" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/cpp/connectivity_module/connectivity_module.cpp" /> diff --git a/pages/advanced-algorithms/available-algorithms/xml_module.mdx b/pages/advanced-algorithms/available-algorithms/xml_module.mdx index bf681e96d..16edf36e6 100644 --- a/pages/advanced-algorithms/available-algorithms/xml_module.mdx +++ b/pages/advanced-algorithms/available-algorithms/xml_module.mdx @@ -16,7 +16,7 @@ loading and parsing XML data. } title="Source code" - href="https://github.com/memgraph/memgraph/blob/master/mage/python/xml_module.py" + href="https://github.com/memgraph/memgraph/blob/master/src/mage/python/xml_module.py" /> diff --git a/pages/advanced-algorithms/install-mage.mdx b/pages/advanced-algorithms/install-mage.mdx index feb131926..81cab9145 100644 --- a/pages/advanced-algorithms/install-mage.mdx +++ b/pages/advanced-algorithms/install-mage.mdx @@ -124,49 +124,25 @@ Memgraph package](/getting-started/install-memgraph). Algorithms and query modules will be loaded into a Memgraph instance on startup once you install MAGE, so make sure your instances are not running. -{

Install dependencies

} - -To build from source, you will need: -- Python3 -- Make -- CMake -- Clang -- UUID -- [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) - -{

Set up the machine

} +{

Download the Memgraph source code

} -Run the following commands: +MAGE is developed and built as part of the Memgraph repository. Clone the +[Memgraph source code](https://github.com/memgraph/memgraph) from GitHub +(install `git` first if you don't have it β€” `sudo apt-get install -y git`): -```bash -sudo apt-get update && sudo apt-get install -y \ - libcurl4 \ - libpython3.12 \ - libssl-dev \ - openssl \ - build-essential \ - cmake \ - curl \ - g++ \ - python3 \ - python3-pip \ - python3-setuptools \ - python3-dev \ - clang \ - git \ - pkg-config \ - uuid-dev \ - xmlsec1 \ - ninja-build \ - --no-install-recommends +``` +git clone https://github.com/memgraph/memgraph.git && cd memgraph/ ``` -{

Download the MAGE source code

} +{

Install dependencies

} -Clone the [Memgraph source code](https://github.com/memgraph/memgraph) from GitHub: +The repository ships scripts that install everything the toolchain and the +build need β€” `build.sh` checks for both sets and stops if anything is +missing: -``` -git clone https://github.com/memgraph/memgraph.git && cd memgraph/ +```bash +sudo ./environment/os/install_deps.sh install TOOLCHAIN_RUN_DEPS +sudo ./environment/os/install_deps.sh install MEMGRAPH_BUILD_DEPS ``` {

Set up the toolchain

} @@ -177,23 +153,18 @@ curl -L https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/toolchain-v7/toolcha sudo tar xzvfm toolchain.tar.gz -C /opt ``` -Install runtime dependencies for the toolchain: -```bash -sudo ./environment/os/install_deps.sh install TOOLCHAIN_RUN_DEPS -``` - {

Install Rust and Python dependencies

} -Run the following command to install Rust and Python dependencies: +Run the following commands from the root of the repository to install Rust +and the Python packages the MAGE query modules use at runtime: ```shell -cd mage -curl https://sh.rustup.rs -sSf | sh -s -- -y -export PATH="/root/.cargo/bin:${PATH}" -python3 -m pip install -r python/requirements.txt -python3 -m pip install -r ../src/auth/reference_modules/requirements.txt -python3 -m pip install torch-sparse torch-cluster torch-spline-conv torch-geometric torch-scatter -f https://data.pyg.org/whl/torch-2.6.0+cpu.html -python3 -m pip install dgl -f https://data.dgl.ai/wheels/torch-2.6/repo.html +source environment/util.sh +install_rust 1.89 +python3 -m pip install -r src/mage/python/requirements.txt +python3 -m pip install -r src/auth/reference_modules/requirements.txt +python3 -m pip install torch-sparse torch-cluster torch-spline-conv torch-geometric torch-scatter -f https://data.pyg.org/whl/torch-2.8.0+cpu.html +python3 -m pip install dgl -f https://data.dgl.ai/wheels/torch-2.8/repo.html ``` @@ -201,46 +172,50 @@ python3 -m pip install dgl -f https://data.dgl.ai/wheels/torch-2.6/repo.html To install the dependencies for GPU-accelerated algorithms, you need to use the GPU-specific requirements file: ```shell -python3 -m pip install -r python/requirements-gpu.txt +python3 -m pip install -r src/mage/python/requirements-gpu.txt ``` -{

Run the `setup` script

} +{

Build and install MAGE

} -Run the following command: +MAGE is built with the same build system as Memgraph. Run the following +commands from the root of the repository: ```shell source /opt/toolchain-v7/activate -python3 setup build -sudo cp -r dist/* /usr/lib/memgraph/query_modules +./build.sh --mage only +sudo cmake --install build --component mage --prefix /usr ``` - +`./build.sh --mage only` builds just the MAGE query modules (C++, Python and +Rust) without Memgraph itself β€” the script sets up everything else it needs +(a Python virtual environment, the Conan package manager and the project's +dependencies) on first run. The built modules land in `build/mage/dist`. -If you don't need all of the algorithms you can build only some of them based on the laguauge. +The `cmake --install` command then installs the modules to +`/usr/lib/memgraph/query_modules`, the directory Memgraph loads query modules +from, together with the runtime libraries they need. -To build C++ based algorithms run: + + +If you don't need all of the algorithms, you can build a subset by passing +specific targets: ```shell -python3 setup build --lang cpp -``` +# Only the Python modules (a copy step - fast) +./build.sh --mage only --target mage_python_modules -To build Python based algorithms run: +# Only the Rust modules +./build.sh --mage only --target mage_rust_modules -```shell -python3 setup build --lang python +# Individual C++ modules by name +./build.sh --mage only --target map text ``` -The script will generate a `dist` directory with all the needed files. - -It will also copy the contents of the newly created `dist` directory to -`/usr/lib/memgraph/query_modules`. Memgraph loads algorithms and modules from -this directory. - -If something isn't installed properly, the `setup` script will stop the -installation process. If you have any questions, contact us on +If something isn't set up properly, the build will stop with an error. If you +have any questions, contact us on **[Discord](https://discord.gg/memgraph).** {

Start a Memgraph instance

} diff --git a/pages/custom-query-modules.mdx b/pages/custom-query-modules.mdx index 2812a598c..8ac636c1a 100644 --- a/pages/custom-query-modules.mdx +++ b/pages/custom-query-modules.mdx @@ -89,14 +89,14 @@ Then, select a language you want to develop in. {

Develop a query

} - When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/mage) and extend based on that. + When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/src/mage) and extend based on that. If you are not extending the existing query modules, you can start from scratch by following these guides or using the API docs and knowledge to develop your query modules. - [Python](/custom-query-modules/python), - [C](/custom-query-modules/c), - [C++](/custom-query-modules/cpp), - - [Rust](https://github.com/memgraph/memgraph/tree/master/mage/rust/rsmgp-example). + - [Rust](https://github.com/memgraph/memgraph/tree/master/src/mage/rust/rsmgp-example). {

Start the MAGE container

} @@ -210,14 +210,14 @@ Then, select a language you want to develop in. {

Develop modules

} - When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/mage) and extend based on that. + When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/src/mage) and extend based on that. If you are not extending the existing query modules, you can start from scratch by following these guides or using the API docs and knowledge to develop your query modules. - [Python](/custom-query-modules/python), - [C](/custom-query-modules/c), - [C++](/custom-query-modules/cpp), - - [Rust](https://github.com/memgraph/memgraph/tree/master/mage/rust/rsmgp-example). + - [Rust](https://github.com/memgraph/memgraph/tree/master/src/mage/rust/rsmgp-example). {

Create the `dev` image

} @@ -279,106 +279,54 @@ Then, select a language you want to develop in. - {

Install dependencies

} - - To build from source, you will need: - - Python3 - - Make - - CMake - - Clang - - UUID - - [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) - - {

Set up the machine

} - - Run the following commands: - - ```bash - PY_VERSION="$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2)" - sudo apt update - sudo apt install -y \ - libcurl4 \ - libpython${PY_VERSION} \ - libssl-dev \ - openssl \ - build-essential \ - cmake \ - curl \ - g++ \ - python3 \ - python3-pip \ - python3-setuptools \ - python3-dev \ - clang \ - git \ - libboost-dev \ - ninja-build \ - --no-install-recommends - ``` - - {

Download the Memgraph source code

} - - Clone the [Memgraph source code](https://github.com/memgraph/memgraph) from GitHub: + {

Set up the build environment

} - ``` - git clone https://github.com/memgraph/memgraph.git && cd memgraph/mage - ``` + Follow the [Build from source + guide](/advanced-algorithms/install-mage#build-from-source-linux) to clone + the Memgraph repository, install the build dependencies, set up the + toolchain and install the Rust and Python dependencies. - {

Run the `setup` script

} + {

Build MAGE

} - Run the following command: + Run the following commands from the root of the repository: ```shell - python3 setup build -p /usr/lib/memgraph/query_modules + source /opt/toolchain-v7/activate + ./build.sh --mage only ``` - The script will generate a `dist` directory with all the necessary files. - - It will also copy the contents of the newly created `dist` directory to - `/usr/lib/memgraph/query_modules`. Memgraph loads algorithms and modules from - this directory. - - If something isn't installed properly, the `setup` script will stop the - installation process. If you have any questions, contact us on - **[Discord](https://discord.gg/memgraph).** - - {
Set a different `query_modules` directory
} + The built query modules land in `build/mage/dist`. - The `setup` script can set your local `mage/dist` directory or any other - directory as the default one by changing the value of the - `--query-modules-directory` flag in the `/etc/memgraph/memgraph.conf`, - Memgraph's configuration file. + {

Install the query modules

} - By setting the `/mage/dist` as the default directory you don't need to copy - `*.so` and `*.py` files from the `mage/dist` directory - to`/usr/lib/memgraph/query_modules` every time you run `build`: + Install the modules to `/usr/lib/memgraph/query_modules`, the directory + Memgraph loads query modules from: - ``` - python3 setup modules_storage + ```shell + sudo cmake --install build --component mage --prefix /usr ``` - By setting `` as the default one, Memgraph will be looking for - query modules inside ``, instead of `/usr/lib/memgraph/query_modules`: + - ``` - python3 setup modules_storage -p - ``` + While developing, you can skip the install step on every rebuild by + pointing Memgraph directly at the build output instead: set + `--query-modules-directory=/build/mage/dist` in + `/etc/memgraph/memgraph.conf` (or on the command line). - Don't forget to copy the aforementioned files from `mage/dist` to - ``. + {

Develop modules

} - When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/mage) and extend based on that. + When developing with Mage, take a look at the basis of developed [algorithms and utility procedures](https://github.com/memgraph/memgraph/tree/master/src/mage) and extend based on that. If you are not extending the existing query modules, you can start from scratch by following these guides or using the API docs and knowledge to develop your query modules. - [Python](/custom-query-modules/python), - [C](/custom-query-modules/c), - [C++](/custom-query-modules/cpp), - - [Rust](https://github.com/memgraph/memgraph/tree/master/mage/rust/rsmgp-example). + - [Rust](https://github.com/memgraph/memgraph/tree/master/src/mage/rust/rsmgp-example). - {

Start Memgraph

} + {

Rebuild and load your changes

} Make sure your Memgraph instance is running: @@ -386,12 +334,18 @@ Then, select a language you want to develop in. sudo systemctl status memgraph.service ``` - {

Copy the query module

} - - Copy your developed query module to `/usr/lib/memgraph/query_modules` or your directory if you changed the default location of query modules: + After changing a module, rebuild and reinstall (the install step isn't + needed if you pointed `--query-modules-directory` at `build/mage/dist`): ```shell - python3 setup build -p /usr/lib/memgraph/query_modules + ./build.sh --mage only --dev + sudo cmake --install build --component mage --prefix /usr + ``` + + Then reload the query modules in a running instance: + + ```cypher + CALL mg.load_all(); ```
diff --git a/pages/custom-query-modules/contributing.mdx b/pages/custom-query-modules/contributing.mdx index 9e8d4fedb..0561f6705 100644 --- a/pages/custom-query-modules/contributing.mdx +++ b/pages/custom-query-modules/contributing.mdx @@ -18,7 +18,7 @@ Here are links to Memgraph and MAGE, which are both opened and ready to receive and your contribution: - [**Memgraph**](https://github.com/memgraph/memgraph) -- [**MAGE**](https://github.com/memgraph/memgraph/tree/master/mage) (NOTE: MAGE now lives in the Memgraph repository) +- [**MAGE**](https://github.com/memgraph/memgraph/tree/master/src/mage) (NOTE: MAGE now lives in the Memgraph repository) Feel free to create an issue or open a pull request on our Github repo to speed up the development. diff --git a/pages/custom-query-modules/cpp/cpp-example.mdx b/pages/custom-query-modules/cpp/cpp-example.mdx index 8604315c4..a3bed2132 100644 --- a/pages/custom-query-modules/cpp/cpp-example.mdx +++ b/pages/custom-query-modules/cpp/cpp-example.mdx @@ -1101,7 +1101,7 @@ cpp To make sure the module is linked with the rest of MAGE code, we need to add a `CMakeLists.txt` script in the new directory and register our module in the `cpp/CMakelists.txt` script as well. Refer to the existing scripts in MAGE’s -[query modules](https://github.com/memgraph/memgraph/tree/master/mage/cpp). +[query modules](https://github.com/memgraph/memgraph/tree/master/src/mage/cpp).
diff --git a/pages/getting-started/packaging-memgraph.mdx b/pages/getting-started/packaging-memgraph.mdx index faa373d84..2c0e37aef 100644 --- a/pages/getting-started/packaging-memgraph.mdx +++ b/pages/getting-started/packaging-memgraph.mdx @@ -9,10 +9,11 @@ import { Steps } from 'nextra/components' # Package Memgraph This guide will show you how to package Memgraph for one of out supportedLinux distributions. -There are two main ways to package Memgraph: +There are three main ways to package Memgraph: - [Using the `mgbuild.sh` script](#using-the-mgbuildsh-script), which builds Memgraph in a Docker container. (Recommended) -- [Using CPack](#using-cpack), which builds Memgraph using CMake. +- [Using the `package.sh` script](#using-the-packagesh-script), which packages a local build directory. +- [Using CPack](#using-cpack), which invokes CPack manually. The distributions of Linux currently supported by Memgraph are and their associated `OS` environment variable: @@ -114,12 +115,41 @@ sudo apt install ./output/memgraph__.deb +## Using the `package.sh` script + +After following the build instructions using +[`build.sh`](/getting-started/build-memgraph-from-source#use-buildsh-script), or +[`conan` and `cmake`](/getting-started/build-memgraph-from-source#use-conan-and-cmake), the +`package.sh` script in the repository root packages the build directory directly on the host. +It takes a target and a package format: + +```bash +source /opt/toolchain-v7/activate +./package.sh memgraph deb +``` + +- `TARGET` is one of `memgraph` (the `memgraph` + `memgraph-debuginfo` packages), `mage` + (the `memgraph-mage` package, plus `memgraph-mage-debuginfo` when the build used + `--split-debug`), or `all` (everything the build was configured for). +- `FORMAT` is `deb` or `rpm`. + +For example, to package the MAGE query modules as an RPM from a +`./build.sh --mage only` build: + +```bash +./package.sh mage rpm +``` + +The script checks that the build directory is actually configured for the requested target +(e.g. packaging `mage` requires a build with `--mage on` or `--mage only`), selects the right +CPack components, and verifies the expected number of packages was produced. The packages are +written to `build/output/`. Use `--build-dir DIR` to package from a non-default build +directory. + ## Using CPack -After following the build instructions using -[`build.sh`](/getting-started/build-memgraph-from-source#use-buildsh-script), or -[`conan` and `cmake`](/getting-started/build-memgraph-from-source#use-conan-and-cmake), one can use -CPackto build a Memgraph package for a Linux distribution. +`package.sh` is a thin wrapper around CPack β€” if you need full control, you can also invoke +CPack manually. Prior to packaging, create the output directory: From 4fbedadd34e801c579d7e9e96cbfd1e2c5f36673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:21:54 +0200 Subject: [PATCH 05/19] docs: add convert module JSON conversion functions (#1699) * docs: document convert from_json_map/list, to_map and to_json Document the four JSON/map conversion functions in the convert module, including the optional path selector, its supported syntax, and the structured to_json output for nodes, relationships, paths, points and temporals. * docs: correct convert.to_map arg name and to_json node example * docs: add APOC-equivalence callouts and mapping-table rows for convert JSON functions * docs: steer json_util JSON functions to the convert module * docs: move convert-module steer to per-function callouts in json_util * docs: render convert map/list results as Cypher values, not JSON * docs: correct convert.to_map description for non-map values * docs: note convert.to_json supported types and enum error --- .../available-algorithms.mdx | 6 +- .../available-algorithms/convert.mdx | 209 +++++++++++++++++- .../available-algorithms/json_util.mdx | 8 +- 3 files changed, 211 insertions(+), 12 deletions(-) diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index b6e6dc234..3b6bf14ed 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -174,8 +174,10 @@ Running `SHOW QUERY CALLABLE MAPPINGS` requires the `CONFIG` privilege. | apoc.coll.sum | Calculates the sum of listed elements | [collections.sum()](/advanced-algorithms/available-algorithms/collections#sum) | | apoc.coll.partition | Partitions the input list into sub-lists of the specified size | [collections.partition()](/advanced-algorithms/available-algorithms/collections#partition) | | apoc.convert.toTree | Converts values into tree structures | [convert_c.to_tree()](/advanced-algorithms/available-algorithms/convert_c#to_tree) | -| apoc.convert.fromJsonList | Converts a JSON string representation of a list into an actual list object | [json_util.from_json_list()](/advanced-algorithms/available-algorithms/json_util#from_json_list) | -| apoc.convert.toJson | Converts any value to its JSON string representation | [json_util.to_json()](/advanced-algorithms/available-algorithms/json_util#to_json) | +| apoc.convert.fromJsonList | Converts a JSON string representation of a list into an actual list object | [convert.from_json_list()](/advanced-algorithms/available-algorithms/convert#from_json_list) | +| apoc.convert.fromJsonMap | Converts a JSON string representation of a map into an actual map object | [convert.from_json_map()](/advanced-algorithms/available-algorithms/convert#from_json_map) | +| apoc.convert.toJson | Converts any value to its JSON string representation | [convert.to_json()](/advanced-algorithms/available-algorithms/convert#to_json) | +| apoc.convert.toMap | Converts a value into a map | [convert.to_map()](/advanced-algorithms/available-algorithms/convert#to_map) | | apoc.create.node | Creates a single node with specified labels and properties | [create.node()](/advanced-algorithms/available-algorithms/create#node) | | apoc.create.nodes | Creates multiple nodes with specified labels and properties | [create.nodes()](/advanced-algorithms/available-algorithms/create#nodes) | | apoc.create.removeProperties | Removes properties from nodes | [create.remove_properties()](/advanced-algorithms/available-algorithms/create#remove_properties) | diff --git a/pages/advanced-algorithms/available-algorithms/convert.mdx b/pages/advanced-algorithms/available-algorithms/convert.mdx index e4890a260..cc784d6d6 100644 --- a/pages/advanced-algorithms/available-algorithms/convert.mdx +++ b/pages/advanced-algorithms/available-algorithms/convert.mdx @@ -54,12 +54,205 @@ Use the following query to convert a JSON string to an object: RETURN convert.str2object('{"name": "Alice", "age": 30, "city": "New York"}') AS result; ``` -The output shows the parsed object: - -```json -{ - "name": "Alice", - "age": 30, - "city": "New York" -} +The output shows the parsed map: + +```plaintext +{name: "Alice", age: 30, city: "New York"} +``` + +### `from_json_map()` + +Parses a JSON-object string into a map. An optional `path` selects a nested part +of the document before conversion; the selected value must be a JSON object. + + +This function is equivalent to **apoc.convert.fromJsonMap**. + + +{

Input:

} + +- `map: String` ➑ The JSON string to parse. A `null` value returns `null`. +- `path: String` (default `""`) ➑ An optional selector for a nested part of the + document (see [Path option](#path-option)). + +{

Output:

} + +- `Map` ➑ The parsed map. Returns `null` when the input is `null`, when the path + does not resolve, or when the selected value is JSON `null`. An error is raised + when the input (or selected value) is not a JSON object. + +{

Usage:

} + +```cypher +RETURN convert.from_json_map('{"name": "GDS"}') AS result; +``` + +```plaintext +{name: "GDS"} +``` + +Select a nested object with `path`: + +```cypher +RETURN convert.from_json_map('{"a": 1, "b": {"c": 2, "d": [10, 20]}}', '$.b') AS result; +``` + +```plaintext +{c: 2, d: [10, 20]} +``` + +To read a single value out of the parsed map, index it with Cypher instead of +using `path`: + +```cypher +RETURN convert.from_json_map('{"mode": "fast"}')['mode'] AS result; +``` + +```text +"fast" +``` + +### `from_json_list()` + +Parses a JSON-array string into a list. An optional `path` selects a nested part +of the document before conversion; the selected value must be a JSON array. + + +This function is equivalent to **apoc.convert.fromJsonList**. + + +{

Input:

} + +- `list: String` ➑ The JSON string to parse. A `null` value returns `null`. +- `path: String` (default `""`) ➑ An optional selector for a nested part of the + document (see [Path option](#path-option)). + +{

Output:

} + +- `List` ➑ The parsed list. Returns `null` when the input is `null`, when the + path does not resolve, or when the selected value is JSON `null`. An error is + raised when the input (or selected value) is not a JSON array. + +{

Usage:

} + +```cypher +RETURN convert.from_json_list('[1, 2, 3]') AS result; +``` + +```plaintext +[1, 2, 3] +``` + +Select a nested array with `path`: + +```cypher +RETURN convert.from_json_list('{"a": [1, 2, 3]}', '$.a') AS result; +``` + +```plaintext +[1, 2, 3] +``` + +### Path option + +`from_json_map()` and `from_json_list()` accept an optional `path` that selects a +nested part of the JSON document before conversion. + +| Syntax | Meaning | Example (on `{"a": 1, "b": {"c": 2, "e": [10, 20]}}`) | +| ------ | ------- | ----------------------------------------------------- | +| `$` / empty / `null` | The whole document | `$` selects the whole object | +| `.key` | Object key step | `$.b` selects `{"c": 2, "e": [10, 20]}` | +| `['key']` or `["key"]` | Quoted key step, for keys with dots, spaces or special characters | `$['b']` selects `{"c": 2, "e": [10, 20]}` | +| `[index]` | Array element, 0-based | `$.b.e[1]` selects `20` | + +Steps chain left to right (`$.b.e[0]` selects `10`), and a leading `$` is +optional (`a.b` is equivalent to `$.a.b`). Wildcards (`$.e[*]`), recursive +descent (`$..x`), filter expressions and array slices are not supported and raise +an error. A path that does not resolve, or that resolves to JSON `null`, returns +`null`. + +### `to_map()` + +Returns a map unchanged, or a node or relationship as its property map. Any +other value β€” such as an integer, string or list β€” returns `null`. + + +This function is equivalent to **apoc.convert.toMap**. + + +{

Input:

} + +- `map: Any` ➑ The value to convert. + +{

Output:

} + +- `Map` ➑ A map is returned unchanged, a node or relationship is returned as its + property map, and `null` or any other value returns `null`. + +{

Usage:

} + +```cypher +CREATE (n:Person {id: 4, name: 'z'}) +RETURN convert.to_map(n) AS result; +``` + +```plaintext +{id: 4, name: "z"} +``` + +### `to_json()` + +Serializes a value into a JSON string. + + +This function is equivalent to **apoc.convert.toJson**. + + +{

Input:

} + +- `value: Any` ➑ The value to serialize. + +{

Output:

} + +- `String` ➑ The JSON string representation of the value. + +Scalars, lists and maps are serialized directly. Graph and spatial-temporal +values use a structured form: + +- **Node** ➑ `{id, type: "node", labels, properties}`; `labels` and `properties` + are omitted when empty. +- **Relationship** ➑ `{id, type: "relationship", label, start, end, properties}`, + where `start` and `end` are full node objects; `properties` is omitted when empty. +- **Path** ➑ a flat array `[node, relationship, node, ...]`. +- **Point** ➑ `{crs, x, y, z}` for cartesian points, `{crs, longitude, latitude, + height}` for geographic points. +- **Temporal** ➑ the canonical string form (for example `"2020-01-02"` for a + date, an ISO-8601 period with a fixed six-digit fractional-second field for a + duration, e.g. `"P1DT2H3M4.500000S"`). + +Object keys are serialized in alphabetical order, and the order of the `labels` +array follows the node's internal label IDs, so it is not guaranteed. + +Null, boolean, numeric, string, list, map, node, relationship, path, point and +temporal values are all supported. Serializing an enum value raises an error. + +{

Usage:

} + +```cypher +RETURN convert.to_json({a: 1, b: 'x', c: [1, 2], d: null}) AS result; +``` + +```text +{"a":1,"b":"x","c":[1,2],"d":null} +``` + +Serialize a node: + +```cypher +CREATE (n:Person:Human {name: 'Ana', age: 30}) +RETURN convert.to_json(n) AS result; +``` + +```text +{"id":"0","labels":["Person","Human"],"properties":{"age":30,"name":"Ana"},"type":"node"} ``` diff --git a/pages/advanced-algorithms/available-algorithms/json_util.mdx b/pages/advanced-algorithms/available-algorithms/json_util.mdx index 2b77d21de..0f2dae62d 100644 --- a/pages/advanced-algorithms/available-algorithms/json_util.mdx +++ b/pages/advanced-algorithms/available-algorithms/json_util.mdx @@ -35,7 +35,9 @@ and if it is a map, the module loads it as a single value. Converts a JSON string representation of a list into an actual list object. -This function is equivalent to **apoc.convert.fromJsonList**. +Prefer the C++ [`convert.from_json_list`](/advanced-algorithms/available-algorithms/convert#from_json_list), +which is what `apoc.convert.fromJsonList` maps to. This Python version can +produce slightly different output. {

Input:

} @@ -69,7 +71,9 @@ Results: Converts any value to its JSON string representation. -This function is equivalent to **apoc.convert.toJson**. +Prefer the C++ [`convert.to_json`](/advanced-algorithms/available-algorithms/convert#to_json), +which is what `apoc.convert.toJson` maps to. This Python version can produce +slightly different output. {

Input:

} From 059e88cd1f60382da31eacdacca0bf25fcbf67ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:54:15 +0200 Subject: [PATCH 06/19] Add search module documentation (search.node, search.node_all) (#1698) * Add search module documentation (search.node, search.node_all) * Add search module to available-algorithms index and APOC mappings * docs: clarify =~ is not string-only-guarded in search operators --------- Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- .../available-algorithms.mdx | 5 +- .../available-algorithms/_meta.ts | 1 + .../available-algorithms/search.mdx | 196 ++++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 pages/advanced-algorithms/available-algorithms/search.mdx diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index 3b6bf14ed..b17899937 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -111,7 +111,8 @@ If you want to know more and learn how this affects you, read our [announcement] | [nodes](/advanced-algorithms/available-algorithms/nodes) | C++ | A module that provides a comprehensive toolkit for managing multiple graph nodes, enabling linking, updating, type deduction and more. | | [periodic](/advanced-algorithms/available-algorithms/periodic) | C++ | A module containing procedures for periodically running difficult and/or memory/time consuming queries. | | [refactor](/advanced-algorithms/available-algorithms/refactor) | C++ | The refactor module provides utilities for changing nodes and relationships. | -| [rust_example](https://github.com/memgraph/memgraph/tree/master/src/mage/rust/rsmgp-example) | Rust | Example of a basic module with input parameters forwarding, made in Rust. | +| [rust_example](https://github.com/memgraph/memgraph/tree/master/mage/rust/rsmgp-example) | Rust | Example of a basic module with input parameters forwarding, made in Rust. | +| [search](/advanced-algorithms/available-algorithms/search) | C++ | A module for finding nodes by comparing one or more of their properties against a value with a comparison operator, without writing the equivalent `MATCH` and `WHERE` clauses. | | [set_property](/advanced-algorithms/available-algorithms/set_property) | C++ | A module for dynamical access and editing of node and relationship properties. | | [temporal](/advanced-algorithms/available-algorithms/temporal) | Python | A module that provides functions to handle temporal (time-related) operations and offers extended capabilities compared to the date module. | | [text](/advanced-algorithms/available-algorithms/text) | C++ | The `text` module offers a toolkit for manipulating strings. | @@ -204,6 +205,8 @@ Running `SHOW QUERY CALLABLE MAPPINGS` requires the `CONFIG` privilege. | apoc.refactor.renameType | Changes the relationship type | [refactor.rename_type()](/advanced-algorithms/available-algorithms/refactor#rename_type) | | apoc.refactor.rename.typeProperty | Renames the property of a relationship | [refactor.rename_type_property()](/advanced-algorithms/available-algorithms/refactor#rename_type_property) | | apoc.refactor.mergeNodes | Merges properties, labels and relationships for source nodes to target node | [refactor.mergeNodes()](/advanced-algorithms/available-algorithms/refactor#mergenodes) | +| apoc.search.node | Finds nodes by a label-property map and comparison operator, returning each matching node once | [search.node()](/advanced-algorithms/available-algorithms/search#node) | +| apoc.search.nodeAll | Finds nodes by a label-property map and comparison operator, returning one row per matching property | [search.node_all()](/advanced-algorithms/available-algorithms/search#node_all) | | apoc.text.join | Joins all strings into a single one with given delimiter | [text.join()](/advanced-algorithms/available-algorithms/text#join) | | apoc.text.indexOf | Finds the index of first occurrence of a substring within a string | [text.indexOf()](/advanced-algorithms/available-algorithms/text#indexof) | | apoc.text.regexGroups | Returns all matched subexpressions of regex on provided text | [text.regexGroups()](/advanced-algorithms/available-algorithms/text#regexgroups) | diff --git a/pages/advanced-algorithms/available-algorithms/_meta.ts b/pages/advanced-algorithms/available-algorithms/_meta.ts index 885416252..d81ceb7e4 100644 --- a/pages/advanced-algorithms/available-algorithms/_meta.ts +++ b/pages/advanced-algorithms/available-algorithms/_meta.ts @@ -59,6 +59,7 @@ export default { "path": "path", "periodic": "periodic", "refactor": "refactor", + "search": "search", "set_cover": "set_cover", "set_property": "set_property", "temporal": "temporal", diff --git a/pages/advanced-algorithms/available-algorithms/search.mdx b/pages/advanced-algorithms/available-algorithms/search.mdx new file mode 100644 index 000000000..18d3d6bcb --- /dev/null +++ b/pages/advanced-algorithms/available-algorithms/search.mdx @@ -0,0 +1,196 @@ +--- +title: search +description: Look up nodes by a label and property value in Memgraph using a comparison operator, without writing the equivalent MATCH and WHERE clauses. +--- + +import { Callout } from 'nextra/components' +import { Cards } from 'nextra/components' +import GitHub from '/components/icons/GitHub' + +# search + +The `search` module finds nodes by comparing one or more of their properties +against a value. You describe which properties to search with a `{label: +property}` map, pick a comparison operator, and provide the value to compare +against, instead of writing the equivalent `MATCH` and `WHERE` clauses yourself. +When a matching label-property index exists, it is used automatically. + + + } + title="Source code" + href="https://github.com/memgraph/memgraph/blob/master/mage/cpp/search_module/search_module.cpp" + /> + + +| Trait | Value | +| ------------------ | ---------- | +| **Module type** | module | +| **Implementation** | C++ | +| **Parallelism** | sequential | + +## Procedures + +Both procedures take the same arguments and differ only in how they handle a +node that matches more than once: `search.node` returns each matching node once, +while `search.node_all` returns one row per property that matches. + +### Arguments + +The `label_property_map` argument names the labels and properties to search. It +is a map from a label to a single property or to a list of properties, for +example `{Person: "name"}` or `{Person: ["name", "email"]}`. When a label maps +to a list of properties, a node matches if **any** of those properties matches +the value. The map may also be given as a JSON string, for example +`'{"Person": "name"}'` or `'{"Person": ["name", "email"]}'`. + +The `operator` argument selects the comparison to apply and is case-insensitive: + +| Operator | Meaning | +| ------------- | ---------------------------------------------------- | +| `=` / `exact` | Equal to the value. | +| `<>` | Not equal to the value. | +| `<` | Less than the value. | +| `<=` | Less than or equal to the value. | +| `>` | Greater than the value. | +| `>=` | Greater than or equal to the value. | +| `starts with` | String starts with the value. | +| `ends with` | String ends with the value. | +| `contains` | String contains the value. | +| `=~` | String matches the value as a regular expression. | + +The `starts with`, `ends with` and `contains` operators only match string +properties; against a non-string property they safely skip the node. The `=~` +regular-expression operator is not guarded this way and should only be used +against string properties. String comparisons are performed by codepoint, so +uppercase letters sort before lowercase ones. + +### `node()` + +Returns each node that matches the search criteria once, even if it matches on +more than one label or property. + + +This procedure is equivalent to **apoc.search.node**. + + +{

Input:

} + +- `label_property_map: Any` ➑ A map (or JSON string) from a label to the property or list of properties to search. +- `operator: string` ➑ The comparison operator to apply. Case-insensitive. +- `value: string` ➑ The value to compare each property against. If `null`, no nodes are returned. + +{

Output:

} + +- `node: Node` ➑ A node matching the search criteria. Each matching node is returned once. + +{

Usage:

} + +Given the following graph: + +```cypher +CREATE (:Person {name: 'Alice'}); +CREATE (:Person {name: 'Bob'}); +CREATE (:Person {name: 'Bobby'}); +CREATE (:Person {name: 'Carol'}); +``` + +The following query returns every `Person` whose `name` is greater than or equal +to `'Bob'`: + +```cypher +CALL search.node({Person: 'name'}, '>=', 'Bob') YIELD node +RETURN node.name AS name ORDER BY name; +``` + +```plaintext ++----------------------------+ +| name | ++----------------------------+ +| "Bob" | +| "Bobby" | +| "Carol" | ++----------------------------+ +``` + +The `label_property_map` can also be given as a JSON string: + +```cypher +CALL search.node('{"Person": "name"}', 'exact', 'Bob') YIELD node +RETURN node.name AS name; +``` + +```plaintext ++----------------------------+ +| name | ++----------------------------+ +| "Bob" | ++----------------------------+ +``` + +When a node carries several of the searched labels, `search.node` still returns +it only once: + +```cypher +CREATE (:P {name: 'x'}); +CREATE (:M {title: 'x'}); +CREATE (:P:M {name: 'x', title: 'x'}); +``` + +```cypher +CALL search.node({P: 'name', M: 'title'}, 'exact', 'x') YIELD node +RETURN count(node) AS c; +``` + +```plaintext ++----------------------------+ +| c | ++----------------------------+ +| 3 | ++----------------------------+ +``` + +### `node_all()` + +Returns a node once for every property that matches the search criteria, so a +node that matches on several properties or labels appears in more than one row. + + +This procedure is equivalent to **apoc.search.nodeAll**. + + +{

Input:

} + +- `label_property_map: Any` ➑ A map (or JSON string) from a label to the property or list of properties to search. +- `operator: string` ➑ The comparison operator to apply. Case-insensitive. +- `value: string` ➑ The value to compare each property against. If `null`, no nodes are returned. + +{

Output:

} + +- `node: Node` ➑ A node matching the search criteria, returned once per matching property. + +{

Usage:

} + +Given the following graph, where one movie matches on `title` and the other on +`tagline`: + +```cypher +CREATE (:Movie {title: 'Matrix', tagline: 'Neo'}); +CREATE (:Movie {title: 'Heat', tagline: 'Matrix'}); +``` + +Searching both properties returns both movies: + +```cypher +CALL search.node_all('{"Movie": ["title", "tagline"]}', 'exact', 'Matrix') YIELD node +RETURN node.title AS title ORDER BY title; +``` + +```plaintext ++----------------------------+ +| title | ++----------------------------+ +| "Heat" | +| "Matrix" | ++----------------------------+ +``` From aab10727c632ac4050d09989ac658556b367efdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:55:17 +0200 Subject: [PATCH 07/19] docs: Document collections NULL handling and add disjunction, subtract, duplicates (#1694) * docs: Document NULL handling for collections functions * docs: Document collections.disjunction, subtract, and duplicates * docs: List all collections functions in the compatibility table and mark their equivalents Add the compatibility-table rows for the collections functions that were missing (including disjunction, subtract, duplicates) and add the equivalence callout to the function pages that lacked one, so every collections function consistently states its compatibility mapping. * docs: Include disjunction, subtract, and duplicates in the NULL-handling matrix --- .../available-algorithms.mdx | 12 ++ .../available-algorithms/collections.mdx | 177 +++++++++++++++++- 2 files changed, 188 insertions(+), 1 deletion(-) diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index b17899937..83c873129 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -174,6 +174,18 @@ Running `SHOW QUERY CALLABLE MAPPINGS` requires the `CONFIG` privilege. | apoc.coll.toSet | Converts the input list to a set | [collections.to_set()](/advanced-algorithms/available-algorithms/collections#to_set) | | apoc.coll.sum | Calculates the sum of listed elements | [collections.sum()](/advanced-algorithms/available-algorithms/collections#sum) | | apoc.coll.partition | Partitions the input list into sub-lists of the specified size | [collections.partition()](/advanced-algorithms/available-algorithms/collections#partition) | +| apoc.coll.sort | Sorts the elements of an input list of the same data type | [collections.sort()](/advanced-algorithms/available-algorithms/collections#sort) | +| apoc.coll.containsSorted | Verifies the presence of an element in a sorted list | [collections.contains_sorted()](/advanced-algorithms/available-algorithms/collections#contains_sorted) | +| apoc.coll.containsAll | Checks if a list contains all the values from another list | [collections.contains_all()](/advanced-algorithms/available-algorithms/collections#contains_all) | +| apoc.coll.intersection | Returns the unique intersection of two lists | [collections.intersection()](/advanced-algorithms/available-algorithms/collections#intersection) | +| apoc.coll.disjunction | Returns the symmetric difference of two lists (elements in exactly one) | [collections.disjunction()](/advanced-algorithms/available-algorithms/collections#disjunction) | +| apoc.coll.subtract | Returns the first list with the elements of the second removed, deduplicated | [collections.subtract()](/advanced-algorithms/available-algorithms/collections#subtract) | +| apoc.coll.duplicates | Returns the values that appear more than once in a list | [collections.duplicates()](/advanced-algorithms/available-algorithms/collections#duplicates) | +| apoc.coll.sumLongs | Calculates the sum of list elements cast to integers | [collections.sum_longs()](/advanced-algorithms/available-algorithms/collections#sum_longs) | +| apoc.coll.avg | Calculates the average of listed elements | [collections.avg()](/advanced-algorithms/available-algorithms/collections#avg) | +| apoc.coll.max | Returns the maximum-value element of the input list | [collections.max()](/advanced-algorithms/available-algorithms/collections#max) | +| apoc.coll.min | Returns the minimum-value element of the input list | [collections.min()](/advanced-algorithms/available-algorithms/collections#min) | +| apoc.coll.split | Splits the provided list based on a specified delimiter | [collections.split()](/advanced-algorithms/available-algorithms/collections#split) | | apoc.convert.toTree | Converts values into tree structures | [convert_c.to_tree()](/advanced-algorithms/available-algorithms/convert_c#to_tree) | | apoc.convert.fromJsonList | Converts a JSON string representation of a list into an actual list object | [convert.from_json_list()](/advanced-algorithms/available-algorithms/convert#from_json_list) | | apoc.convert.fromJsonMap | Converts a JSON string representation of a map into an actual map object | [convert.from_json_map()](/advanced-algorithms/available-algorithms/convert#from_json_map) | diff --git a/pages/advanced-algorithms/available-algorithms/collections.mdx b/pages/advanced-algorithms/available-algorithms/collections.mdx index 4c6c7920c..bf80aa9d0 100644 --- a/pages/advanced-algorithms/available-algorithms/collections.mdx +++ b/pages/advanced-algorithms/available-algorithms/collections.mdx @@ -39,11 +39,53 @@ uncomparable.
Only `Numeric` data types can be used in `sum()` and `avg()` functions, so only `Int` and `Double` data types are allowed. +### Handling `NULL` values + +Passing `NULL` as a whole list (or scalar) argument no longer raises an +argument-validation error. Each function returns a defined result: + +| Function | Result when a list argument is `NULL` | +| --- | --- | +| `sum()`, `sum_longs()`, `avg()`, `min()`, `max()`, `to_set()`, `pairs()` | `null` | +| `union()`, `union_all()`, `disjunction()` | the other list; `null` when both are `NULL` | +| `remove_all()`, `subtract()` | `null` when the first list is `NULL`; the first list when the second is `NULL` | +| `intersection()`, `sort()`, `flatten()`, `duplicates()` | empty list `[]` | +| `contains()`, `contains_all()`, `contains_sorted()` | `false` | +| `frequencies_as_map()` | empty map `{}` | +| `split()`, `partition()` | no rows | + +`NULL` elements inside a list are handled per function: + +- `min()` and `max()` skip `NULL` elements; a list containing only `NULL` values returns `null`. +- `contains()` and `contains_all()` never match a `NULL` search value (`NULL = NULL` is not true), so searching for `NULL` returns `false`. +- `split()` treats a `NULL` delimiter as matching nothing and returns the whole list as a single part; `NULL` list elements are kept. +- `frequencies_as_map()` counts `NULL` elements under the `"NO_VALUE"` key. +- `flatten()`, `pairs()`, `to_set()`, `union()`, `union_all()`, `remove_all()`, `intersection()`, `disjunction()`, `subtract()` and `duplicates()` keep `NULL` elements as ordinary values. +- `sum()`, `sum_longs()`, `avg()`, `sort()` and `contains_sorted()` still reject a list that contains a `NULL` element; `contains_sorted()` also rejects a `NULL` search value, and `partition()` still rejects a `NULL` size. + +For example, a `NULL` argument returns the defined result instead of erroring: + +```cypher +RETURN collections.sum(null) AS sum, collections.sort(null) AS sorted; +``` + +```plaintext ++----------------------------+ +| sum | sorted | ++----------------------------+ +| null | [] | ++----------------------------+ +``` + ### `sort()` Sorts the elements of an input list if they are of the same data type. For the input list to be sorted, its elements must be comparable and of the same type. + +This function is equivalent to **apoc.coll.sort**. + + {

Input:

} - `coll: List[Any]` ➑ List of elements that need to be sorted. @@ -74,6 +116,10 @@ Verifies the presence of a certain element in a sorted list. If an unsorted list is passed, there is no guarantee that the result will be correct. For the input list to be sorted, its elements must be comparable and of the same type. + +This function is equivalent to **apoc.coll.containsSorted**. + + {

Input:

} - `coll: List[Any]` ➑ The target list where the element is searched for. @@ -239,6 +285,10 @@ RETURN collections.contains([1,2,3], "e") AS output; Checks if a list contains all the values from another list. + +This function is equivalent to **apoc.coll.containsAll**. + + {

Input:

} - `coll: List[Any]` ➑ The target list used for searching values. @@ -270,6 +320,10 @@ RETURN collections.contains_all([1, 2, 3, "pero"], [1, 1, 1, 1, 2, 3]) AS contai Returns the unique intersection of two lists. + +This function is equivalent to **apoc.coll.intersection**. + + {

Input:

} - `first: List[Any]` ➑ The first list. @@ -295,6 +349,107 @@ RETURN collections.intersection([1, 1, 2, 3, 4, 5], [1, 1, 3, 5, 7, 9]) AS inter +---------------------------------------------------------+ ``` +### `disjunction()` + +Returns the disjunction (symmetric difference) of two lists: the unique elements +present in exactly one of the lists. The order of the result is not guaranteed. + + +This function is equivalent to **apoc.coll.disjunction**. + + +{

Input:

} + +- `list1: List[Any]` ➑ The first list. +- `list2: List[Any]` ➑ The second list. + +{

Output:

} + +- `List[Any]` ➑ The unique elements found in only one of the two lists. + +{

Usage:

} + +The following query will return the elements present in only one of the lists: + +```cypher +RETURN collections.disjunction([1, 2, 3, 4, 5], [3, 4, 5]) AS disjunction; +``` + +```plaintext ++---------------------------------------------------------+ +| disjunction | ++---------------------------------------------------------+ +| [1, 2] | ++---------------------------------------------------------+ +``` + +### `subtract()` + +Returns the first list as a set with all elements of the second list removed. The +result is deduplicated and its order is not guaranteed. + + +This function is equivalent to **apoc.coll.subtract**. + + +{

Input:

} + +- `list1: List[Any]` ➑ The list to subtract from. +- `list2: List[Any]` ➑ The list of elements to remove. + +{

Output:

} + +- `List[Any]` ➑ The unique elements of the first list that are not present in the second list. + +{

Usage:

} + +The following query will remove the elements of the second list from the first: + +```cypher +RETURN collections.subtract([1, 2, 3, 4, 5, 6, 6], [3, 4, 5]) AS subtracted; +``` + +```plaintext ++---------------------------------------------------------+ +| subtracted | ++---------------------------------------------------------+ +| [1, 2, 6] | ++---------------------------------------------------------+ +``` + +### `duplicates()` + +Returns the values that appear more than once in a list, each reported a single +time, in the order in which the duplicate is first observed. + + +This function is equivalent to **apoc.coll.duplicates**. + + +{

Input:

} + +- `coll: List[Any]` ➑ The input list. + +{

Output:

} + +- `List[Any]` ➑ The values that occur more than once in the input list. + +{

Usage:

} + +The following query will return the values that appear more than once: + +```cypher +RETURN collections.duplicates([1, 1, 2, 3, 3, 3]) AS duplicates; +``` + +```plaintext ++---------------------------------------------------------+ +| duplicates | ++---------------------------------------------------------+ +| [1, 3] | ++---------------------------------------------------------+ +``` + ### `flatten()` Returns flattened list of inputs provided. @@ -468,6 +623,10 @@ RETURN collections.sum([1, 2.3, -4, a.id]) AS sum; Calculates the sum of list elements casted to integers. The initial list elements have to be `Numeric` data type, or an exception is thrown. + +This function is equivalent to **apoc.coll.sumLongs**. + + {

Input:

} - `numbers: List[Any]` ➑ The list of numbers. @@ -498,6 +657,10 @@ Calculates the average of listed elements if they are of the same type and can be summed (the elements need to be numerics). Listing elements of different data types, or data types that are impossible to sum, will throw an exception. + +This function is equivalent to **apoc.coll.avg**. + + {

Input:

} - `numbers: List[Any]` ➑ The list of numbers. @@ -526,6 +689,10 @@ RETURN collections.avg([5, 5, 6, 7, -5]) AS average; The procedure returns the element of the maximum value from the input list. + +This function is equivalent to **apoc.coll.max**. + + {

Input:

} - `values: List[Any]` ➑ The input list where an element of the maximum value must be found. @@ -556,6 +723,10 @@ Finds the element of the minimum value in an input list. Listing elements of different data types, or data types that are impossible to compare, will throw an exception. + +This function is equivalent to **apoc.coll.min**. + + {

Input:

} - `values: List[Any]` ➑ The input list where an element of the minimum value must be found. @@ -588,6 +759,10 @@ Splits the provided list based on a specified delimiter. Returns a series of sublists generated by breaking the original list wherever the delimiter is encountered. The delimiter itself is not included in the resulting sublists. + +This procedure is equivalent to **apoc.coll.split**. + + {

Input:

} - `subgraph: Graph` (**OPTIONAL**) ➑ A specific subgraph, which is an [object of type Graph](/advanced-algorithms/run-algorithms#run-procedures-on-subgraph) returned by the `project()` function, on which the algorithm is run. @@ -660,4 +835,4 @@ RETURN result; +---------------------------------------------------------+ | [5,6] | +---------------------------------------------------------+ -``` \ No newline at end of file +``` From 2b6f7711f344e5432ab1122fb85882ecdb8d45f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:42:07 +0200 Subject: [PATCH 08/19] feat: document text.compare_cleaned function (#1691) --- .../available-algorithms.mdx | 1 + .../available-algorithms/text.mdx | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index 83c873129..c648e57db 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -225,6 +225,7 @@ Running `SHOW QUERY CALLABLE MAPPINGS` requires the `CONFIG` privilege. | apoc.text.format | Formats strings using the C++ fmt library | [text.format()](/advanced-algorithms/available-algorithms/text#format) | | apoc.text.replace | Replaces substrings matching regex with replacement | [text.replace()](/advanced-algorithms/available-algorithms/text#replace) | | apoc.text.regReplace | Replaces substrings matching regex with replacement | [text.regReplace()](/advanced-algorithms/available-algorithms/text#regreplace) | +| apoc.text.compareCleaned | Compares two strings for equality after normalization | [text.compare_cleaned()](/advanced-algorithms/available-algorithms/text#compare_cleaned) | | apoc.util.md5 | Gets MD5 hash of concatenated string representations | [util_module.md5()](/advanced-algorithms/available-algorithms/util_module#md5) | | apoc.util.validatePredicate | Raises exception if predicate yields true with parameter interpolation | [mgps.validate_predicate()](/advanced-algorithms/available-algorithms/mgps#validate_predicate) | | db.awaitIndexes | No-op compatibility shim for clients that wait for index creation (e.g. the Neo4j Spark connector) | [mgps.await_indexes()](/advanced-algorithms/available-algorithms/mgps#await_indexes) | diff --git a/pages/advanced-algorithms/available-algorithms/text.mdx b/pages/advanced-algorithms/available-algorithms/text.mdx index 2966458bb..131ca5439 100644 --- a/pages/advanced-algorithms/available-algorithms/text.mdx +++ b/pages/advanced-algorithms/available-algorithms/text.mdx @@ -297,3 +297,46 @@ Result: | 1 | +--------+ ``` + +### `compare_cleaned()` + +Compares two strings for equality after normalizing each one: keeping only ASCII +letters and digits, converting them to lowercase, and dropping everything else +(accents, punctuation, whitespace, and non-ASCII characters). + + +This function is equivalent to **apoc.text.compareCleaned**. + + + +Normalization is limited to ASCII and performs no Unicode folding, so accented +and non-ASCII letters are dropped rather than reduced to a base letter. For +example, `café` cleans to `caf` and is therefore not equal to `cafe`. + + +{

Input:

} + +- `text1: string` ➑ The first string to normalize and compare. A `null` value results in `false`. +- `text2: string` ➑ The second string to normalize and compare. A `null` value results in `false`. + +{

Output:

} + +- `boolean` ➑ `true` if the two normalized strings are equal, and `false` otherwise. + +{

Usage:

} + +Use the following query to compare two strings while ignoring case and punctuation: + +```cypher +RETURN text.compare_cleaned("Hello, World!", "hello world") AS result; +``` + +Result: + +```plaintext ++--------+ +| result | ++--------+ +| true | ++--------+ +``` From 3ad21aa955b04d19f7484d195e4606097b50cd74 Mon Sep 17 00:00:00 2001 From: Vlasta Date: Wed, 29 Jul 2026 16:48:20 +0200 Subject: [PATCH 09/19] Add Memgraph v3.13.0 release notes changelog entries. Co-authored-by: Cursor --- pages/release-notes.mdx | 113 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index e12028e7e..e01e0d244 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -48,6 +48,119 @@ guide. ### Memgraph v3.13.0 - September 9th, 2026 +{

⚠️ Breaking changes

} + +- The APOC compatibility names `apoc.convert.fromJsonList` and + `apoc.convert.toJson` now resolve to the C++ `convert` module + (`convert.from_json_list` / `convert.to_json`) instead of the Python + `json_util` implementations. Output can differ β€” especially for nodes, + relationships, paths, points, and temporals. Call `json_util.*` explicitly if + you need the previous Python behavior, or update consumers for the new + structured / ISO-8601 JSON forms. + [#4443](https://github.com/memgraph/memgraph/pull/4443) + +{

🐞 Bug fixes

} + +- Text and vector search now respect fine-grained label and property + permissions. Hits the caller cannot read are filtered out, and + `text_search.aggregate` / `text_search.aggregate_edges` return an error unless + every indexed property is readable. + [#4316](https://github.com/memgraph/memgraph/pull/4316) +- Fixed unbounded memory growth from the query AST cache when many + structurally unique queries were run (for example streams of inlined + embeddings). The cache is now a bounded LRU controlled by + `--query-ast-cache-max-size` (default `1000`; `0` disables it). + [#4334](https://github.com/memgraph/memgraph/pull/4334) +- The `collections.*` functions and procedures now accept `NULL` list and scalar + arguments instead of failing argument validation. Each function returns a + defined result (for example `collections.sum(null)` β†’ `null`, + `collections.sort(null)` β†’ `[]`, `collections.contains(null, x)` β†’ `false`), + matching the compatibility-layer behavior. + [#4416](https://github.com/memgraph/memgraph/pull/4416) +- Fixed stale coordinator entries in the HA routing table and a possible + coordinator crash on `REMOVE COORDINATOR`. Coordinators now deduplicate + entries when applying Raft logs and snapshots. + [#4444](https://github.com/memgraph/memgraph/pull/4444) +- Fixed `path.subgraph_all` and `path.subgraph_nodes` so their config options + work as expected. + [#4447](https://github.com/memgraph/memgraph/pull/4447) +- Under heavy concurrent load, operations that need exclusive access to the + database could hang indefinitely while shared readers kept arriving. Those + exclusive waiters now take priority so the hang no longer happens. + [#4450](https://github.com/memgraph/memgraph/pull/4450) +- Fixed a crash when `LOAD PARQUET` hit invalid values (for example a time + outside Memgraph’s supported range). The query now fails with an error + instead of taking down the server. + [#4454](https://github.com/memgraph/memgraph/pull/4454) +- Fixed `LOAD PARQUET` silently storing wrong timestamps or durations when + converting large temporal values from Parquet. Those cases now fail with a + clear query error instead of corrupting data. + [#4455](https://github.com/memgraph/memgraph/pull/4455) +- Fixed a rare crash in garbage collection under heavy concurrent writes when + indexes or unique constraints are defined. + [#4475](https://github.com/memgraph/memgraph/pull/4475) +- Fixed a crash when multiple sessions printed query plans at the same time + (for example concurrent `EXPLAIN` / `PROFILE`, or query-plan logging). + [#4489](https://github.com/memgraph/memgraph/pull/4489) + +{

πŸ› οΈ Improvements

} + +- MAGE is now built and packaged from the same unified CMake / Conan tree as + Memgraph. Build it with `build.sh --mage on` or `--mage only`; the old + `mage/setup` Python script is removed. Only affects people building MAGE from + source β€” prebuilt packages are unchanged. + [#4348](https://github.com/memgraph/memgraph/pull/4348) +- `build.sh --target` now accepts several targets in one invocation (for + example `--target memgraph memgraph__unit`), so you can build multiple + targets with a single configure step instead of one run per target. + [#4404](https://github.com/memgraph/memgraph/pull/4404) +- Reduced memory used by the query abstract syntax tree and plan caches by + storing each LRU cache key once instead of twice per entry. + [#4445](https://github.com/memgraph/memgraph/pull/4445) +- Queries that use query-module functions or `CALL` procedures are now + plan-cached instead of being re-parsed and re-planned on every run. Module + reloads stay safe; only module-dependent queries are invalidated, and the + next execution picks up updated code. + [#4461](https://github.com/memgraph/memgraph/pull/4461) + [#4482](https://github.com/memgraph/memgraph/pull/4482) +- In in-memory transactional mode, `CREATE INDEX` / `CREATE CONSTRAINT` no + longer stall behind garbage collection (and GC no longer waits on those DDL + statements). Under index-heavy workloads this removes GC-correlated latency + spikes on index and constraint creation. + [#4468](https://github.com/memgraph/memgraph/pull/4468) + +{

✨ New features

} + +- Added `collections.disjunction`, `collections.subtract`, and + `collections.duplicates` to the MAGE `collections` module for set-style list + difference, subtraction, and finding repeated values. + [#4415](https://github.com/memgraph/memgraph/pull/4415) +- Added `map.get` and `map.merge_list` to the MAGE `map` module. Existing + helpers such as `map.merge` and `map.set_key` now treat a `null` map as empty + (and a `null` key as a no-op) instead of failing, and accept nodes or + relationships by using their property maps. + [#4417](https://github.com/memgraph/memgraph/pull/4417) +- Added `text.compare_cleaned(text1, text2)` to the MAGE `text` module. It + compares two strings for equality after keeping only ASCII letters and digits + (lowercased) and dropping everything else; `NULL` arguments return `false`. + [#4435](https://github.com/memgraph/memgraph/pull/4435) +- Added `convert.from_json_map`, `convert.from_json_list`, `convert.to_map`, and + `convert.to_json` for parsing and serializing JSON, with an optional `path` + selector on the from_* helpers and structured output for graph and temporal + types from `to_json`. + [#4443](https://github.com/memgraph/memgraph/pull/4443) +- Added `search.node` and `search.node_all` procedures that find nodes by a + `{label: property}` map, a comparison operator, and a value. A property list + per label is OR-ed; `search.node` de-duplicates by node while + `search.node_all` keeps duplicates. An existing label-property index is used + automatically when available. + [#4460](https://github.com/memgraph/memgraph/pull/4460) +- `--coordinator-id=0` is now a valid coordinator id (previously rejected). + [#4483](https://github.com/memgraph/memgraph/pull/4483) +- `CREATE RANGE INDEX FOR ... ON ...` now works for nodes and relationships and + creates Memgraph’s usual property index. + [#4486](https://github.com/memgraph/memgraph/pull/4486) + ### Lab v3.13.0 - September 9th, 2026 From c9b8c018605afa768435e1f36e693c7b5716954b Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 11:52:14 +0200 Subject: [PATCH 10/19] docs: HA analytical mode bulk import (#1724) * docs: HA analytical mode bulk import (memgraph#4510) Document the analytical bulk-import workflow for data instances: switch to IN_MEMORY_ANALYTICAL, import, switch back to IN_MEMORY_TRANSACTIONAL and register the replicas again. - New page clustering/high-availability/analytical-import.mdx with the requirements (MAIN role, zero registered replicas, instance-wide), the step-by-step recipe, the durability guarantees of the switch back, replica re-registration without wiping, and the user-facing error messages. - Storage modes page: drop the "doesn't support replication and high availability" claim, correct the stale "create a snapshot manually before switching back" advice, describe the switch-back snapshot, .old archiving and WAL sequence restart. - Replication and HA command references and best practices: registration and unregistration belong in transactional mode only. - Release notes: breaking change and new feature entries for v3.13.0. - Fix the flag name --storage-enable-backup-dir -> --storage-backup-dir-enabled, which never existed under the documented name. * docs: improvements --- pages/clustering/high-availability.mdx | 5 + pages/clustering/high-availability/_meta.ts | 1 + .../high-availability/analytical-import.mdx | 222 ++++++++++++++++++ .../high-availability/best-practices.mdx | 14 ++ .../ha-commands-reference.mdx | 17 ++ .../how-high-availability-works.mdx | 2 +- .../clustering/replication/best-practices.mdx | 25 +- .../replication-commands-reference.mdx | 12 + .../backup-and-restore.mdx | 2 +- pages/database-management/configuration.mdx | 4 +- pages/fundamentals/data-durability.mdx | 2 +- pages/fundamentals/storage-memory-usage.mdx | 41 +++- pages/release-notes.mdx | 13 +- 13 files changed, 339 insertions(+), 21 deletions(-) create mode 100644 pages/clustering/high-availability/analytical-import.mdx diff --git a/pages/clustering/high-availability.mdx b/pages/clustering/high-availability.mdx index be6a01e31..d7f8dbcca 100644 --- a/pages/clustering/high-availability.mdx +++ b/pages/clustering/high-availability.mdx @@ -49,6 +49,11 @@ recommended configuration patterns. Recommended practices for running a robust, reliable, and well-observed HA deployment. +### [Bulk import in analytical mode](/clustering/high-availability/analytical-import) + +Import data into a cluster using the in-memory analytical storage mode, then +switch back to transactional mode and register the replicas again. + ### [HA commands reference guide](/clustering/high-availability/ha-commands-reference) A complete reference of all commands for managing coordinators, registering diff --git a/pages/clustering/high-availability/_meta.ts b/pages/clustering/high-availability/_meta.ts index f09ece7ce..8a617d6ce 100644 --- a/pages/clustering/high-availability/_meta.ts +++ b/pages/clustering/high-availability/_meta.ts @@ -5,6 +5,7 @@ export default { "setup-ha-cluster-docker-compose": "Set up HA cluster with Docker Compose", "setup-ha-cluster-k8s": "Set up HA cluster with K8s", "best-practices": "Best practices", + "analytical-import": "Bulk import in analytical mode", "ha-commands-reference": "Reference commands", "ha-reference-architectures": "Reference architectures", "migrating-to-v3-9-ha": "Migrating to v3.9 HA", diff --git a/pages/clustering/high-availability/analytical-import.mdx b/pages/clustering/high-availability/analytical-import.mdx new file mode 100644 index 000000000..6602f7f86 --- /dev/null +++ b/pages/clustering/high-availability/analytical-import.mdx @@ -0,0 +1,222 @@ +--- +title: Bulk import in analytical mode +description: Import data into a high availability cluster using the in-memory analytical storage mode, then switch back to transactional and register the replicas again. +--- + +import { Callout } from 'nextra/components' +import { Steps } from 'nextra/components' +import {CommunityLinks} from '/components/social-card/CommunityLinks' + +# Bulk import in analytical mode Enterprise + +The [in-memory analytical storage +mode](/fundamentals/storage-memory-usage#in-memory-analytical-storage-mode) +imports data up to 6 times faster and with significantly lower memory usage than +the transactional mode, because it does not create `Delta` objects. As of +Memgraph v3.13, a data instance in a high availability cluster can use that mode +for a bulk import: switch to `IN_MEMORY_ANALYTICAL`, import, switch back to +`IN_MEMORY_TRANSACTIONAL` and register the replicas again. + +Analytical writes are **never written to the WAL**, so they cannot be +replicated. The whole workflow is therefore built around a single rule: while +any database on the instance is analytical, the instance must have no replicas +attached to it. + + + +Before continuing, read the guides on [how high availability +works](/clustering/high-availability/how-high-availability-works) and on +[storage modes](/fundamentals/storage-memory-usage#storage-modes). + + + +## Requirements + +A data instance can enter the analytical storage mode only when: + +- it holds the **MAIN** role β€” a REPLICA receives replicated writes, which + analytical mode cannot apply, and +- it has **zero registered replicas**. + +Both conditions are instance-wide and all-or-nothing: + +- entering analytical mode on **any** database requires that no replica is + registered at all, and +- while **any** database is in analytical mode, registering and unregistering + replicas is rejected. + +Because [`UNREGISTER +INSTANCE`](/clustering/high-availability/ha-commands-reference#unregister-instance) +refuses to remove the current MAIN, the instance you import into is the one data +instance that stays in the cluster. + + + +While the import runs, the cluster consists of a single data instance and has +**no redundancy**: there is no replica to fail over to. Plan the import for a +maintenance window and keep it as short as possible. + + + +Starting a data instance with +[`--storage-mode=IN_MEMORY_ANALYTICAL`](/database-management/configuration) +remains forbidden. An instance started in analytical mode can never produce WAL +files, not even after switching to transactional mode, which would permanently +break replication. Analytical mode is reachable only through the runtime +`STORAGE MODE` query from a transactional start. + +## Import workflow + + + +### Unregister every replica + +On the **coordinator**, remove all data instances except the MAIN: + +```cypher +UNREGISTER INSTANCE instance_2; +``` + +The unregistered instance keeps running and keeps its data β€” unregistering +removes it from the cluster, it does not wipe it. You do **not** need to clear +its data directory before registering it back. + +### Switch the MAIN to analytical mode + +On the **MAIN data instance**: + +```cypher +STORAGE MODE IN_MEMORY_ANALYTICAL; +``` + +If a replica is still registered, the query fails and the storage mode is +unchanged. + +### Import the data + +Run the import as usual, for example with [`LOAD +CSV`](/data-migration/csv) or plain Cypher: + +```cypher +LOAD CSV FROM "/import/nodes.csv" WITH HEADER AS row +CREATE (:Node {id: row.id}); +``` + +### Switch back to transactional mode + +```cypher +STORAGE MODE IN_MEMORY_TRANSACTIONAL; +``` + +This writes a snapshot of the imported data synchronously, before the mode +change completes. Do **not** run `CREATE SNAPSHOT` while still in analytical +mode β€” see [Durability of the switch back](#durability-of-the-switch-back). + +### Register the replicas back + +On the **coordinator**: + +```cypher +REGISTER INSTANCE instance_2 WITH CONFIG { + "bolt_server": "localhost:7688", + "management_server": "localhost:10012", + "replication_server": "localhost:10002" +}; +``` + +The replica recovers from the snapshot written in the previous step and ends up +with exactly the data the MAIN holds. Verify with `SHOW INSTANCES` on the +coordinator and by counting nodes on the replica's own Bolt endpoint. + + + +## Durability of the switch back + +The `IN_MEMORY_ANALYTICAL` β†’ `IN_MEMORY_TRANSACTIONAL` switch is the point at +which the imported data becomes durable, so it does more than flip a flag: + +- **It writes a snapshot** stamped with a timestamp that covers the import, and + publishes that timestamp as the instance's last durable timestamp. This is + what a re-registered replica recovers from. +- **If the snapshot cannot be written, the switch is aborted** and the query + throws. The database stays in analytical mode with its data and all durability + files untouched, so you can fix the cause (most often disk space) and retry. +- **Superseded snapshots and WAL files are archived**. The switch-back snapshot + is a new durability base rather than an increment on the old one, so older + snapshots and all WAL files are moved to the `.old` directory β€” or deleted + when [`--storage-backup-dir-enabled`](/database-management/configuration) is + set to `false` β€” and WAL sequence numbering restarts from 0. +- **The WAL is finalized when analytical mode is entered**, which is what lets + replica recovery detect that the imported data exists in no WAL file and must + be shipped as a snapshot. + + + +Do not run `CREATE SNAPSHOT` between the import and the switch back. A snapshot +taken while the instance is in analytical mode carries the **pre-import** +durable timestamp, because analytical writes never advance it. It is redundant +at best β€” the switch back writes a correctly stamped snapshot on its own. + + + +## Re-registering a replica that holds old data + +A replica that was unregistered before the import keeps whatever it had at that +moment, and it may be strictly behind the MAIN. When you register it back, +Memgraph compares the newest snapshot's timestamp against the ranges of the WAL +files. The analytical episode leaves a gap that no WAL covers, so recovery +detects that the WAL chain cannot reproduce the imported data and sends the +snapshot instead of the WAL files. + +The consequences for you as an operator: + +- There is **no need to wipe the replica's data directory** before registering + it back. +- The detection is derived from the durability files, not from in-memory state, + so it also works if the MAIN is **restarted** between the import and the + re-registration. + +## Cluster changes while the MAIN is still analytical + +`REGISTER INSTANCE` and `UNREGISTER INSTANCE` are committed to the Raft log +first and only then applied on the MAIN over RPC, so both queries still report +success while the MAIN is analytical, even though the MAIN rejects the RPC and +logs the reason: + +- **`REGISTER INSTANCE`** β€” the instance is part of the cluster state, but no + replication client is created for it, so it receives nothing. +- **`UNREGISTER INSTANCE`** β€” the instance is removed from the cluster state, + but the MAIN keeps its replication client. + +In both cases the [reconciliation +loop](/clustering/high-availability/how-high-availability-works#how-the-reconciliation-loop-works) +resolves the difference on its own once every database is back in transactional +mode. Still, a query reported as successful does not mean the replica is +attached or detached yet: change the cluster composition only while the cluster +is in transactional mode, and confirm the state with `SHOW INSTANCES` on the +coordinator. + +## Errors + +| Query | Error | Cause | +|-------|-------|-------| +| `STORAGE MODE IN_MEMORY_ANALYTICAL` | `Only the MAIN data instance can use analytical mode.` | The instance holds the REPLICA role. | +| `STORAGE MODE IN_MEMORY_ANALYTICAL` | `Cannot switch to analytical mode while replicas are registered (...)` | At least one replica is registered. The message lists the names. | +| `STORAGE MODE IN_MEMORY_TRANSACTIONAL` | `Failed to create the snapshot required to leave IN_MEMORY_ANALYTICAL.` | The switch-back snapshot could not be written. The database stays analytical and unchanged. | +| `REGISTER REPLICA` | `Couldn't register replica ... because a database is in analytical storage mode.` | Some database on the instance is analytical. | +| `DROP REPLICA` | `Couldn't unregister replica ... because a database is in analytical storage mode.` | Some database on the instance is analytical. | + +The same gates apply to the RPCs the coordinator sends behind `REGISTER +INSTANCE` and `UNREGISTER INSTANCE`; there the rejection is visible in the data +instance's log rather than in the query result. + +## Plain replication clusters + +The gates are not specific to high availability. In a [replication +cluster](/clustering/replication) without coordinators, a MAIN with registered +replicas is likewise refused the switch to analytical mode, and `REGISTER +REPLICA` / `DROP REPLICA` are refused while any database is analytical. Use the +same workflow with `DROP REPLICA` and `REGISTER REPLICA` in place of the +coordinator queries. + + diff --git a/pages/clustering/high-availability/best-practices.mdx b/pages/clustering/high-availability/best-practices.mdx index 80fb03e94..39af74b7d 100644 --- a/pages/clustering/high-availability/best-practices.mdx +++ b/pages/clustering/high-availability/best-practices.mdx @@ -189,6 +189,20 @@ the command line argument. +## Storage mode + +Data instances run in the `IN_MEMORY_TRANSACTIONAL` storage mode. Analytical +writes are not written to the WAL and therefore cannot be replicated, so a data +instance can enter `IN_MEMORY_ANALYTICAL` only when it holds the MAIN role and +has no registered replicas, and replicas can be registered or unregistered only +while every database is transactional. + +For a fast bulk import, unregister the replicas, switch the MAIN to analytical +mode, import, switch back to transactional mode and register the replicas again. +The full recipe, together with the durability guarantees of the switch back, is +in [Bulk import in analytical +mode](/clustering/high-availability/analytical-import). + ## Observability Monitoring cluster health is essential. Key metrics include: diff --git a/pages/clustering/high-availability/ha-commands-reference.mdx b/pages/clustering/high-availability/ha-commands-reference.mdx index 40afbf51d..d91a1976b 100644 --- a/pages/clustering/high-availability/ha-commands-reference.mdx +++ b/pages/clustering/high-availability/ha-commands-reference.mdx @@ -175,6 +175,13 @@ REGISTER INSTANCE instanceName ( AS ASYNC | AS STRICT_SYNC ) ? WITH CONFIG { - In Kubernetes, use service DNS names (e.g. `memgraph-data-1.default.svc.cluster.local`). - Local development uses `localhost`. +- Register instances only while every database on the MAIN is in the + `IN_MEMORY_TRANSACTIONAL` storage mode. If the MAIN is in analytical mode, the + query still returns success (the Raft commit is the success criterion) but the + replica is not attached until the MAIN switches back, at which point the + reconciliation loop attaches it. The data instance logs the reason for the + rejection. See [Bulk import in analytical + mode](/clustering/high-availability/analytical-import). {

Example

} @@ -205,6 +212,16 @@ UNREGISTER INSTANCE instanceName; the replica from MAIN fails, the [reconciliation loop](/clustering/high-availability/how-high-availability-works#how-the-reconciliation-loop-works) automatically retries the operation. +- The unregistered instance keeps running and keeps all of its data. It is + removed from the cluster, not wiped, so you do not need to clear its data + directory before registering it back. +- The MAIN refuses the unregister RPC while any of its databases is in the + `IN_MEMORY_ANALYTICAL` storage mode, and logs the reason. The instance is + still removed from the Raft state, but the MAIN keeps its replication client + until the reconciliation loop retries once every database is transactional + again. Unregister instances only while the cluster is in transactional mode β€” + see [Bulk import in analytical + mode](/clustering/high-availability/analytical-import). {

Example

} diff --git a/pages/clustering/high-availability/how-high-availability-works.mdx b/pages/clustering/high-availability/how-high-availability-works.mdx index 0773a4442..53056a487 100644 --- a/pages/clustering/high-availability/how-high-availability-works.mdx +++ b/pages/clustering/high-availability/how-high-availability-works.mdx @@ -510,7 +510,7 @@ it undergoes a controlled recovery process: is reused on subsequent recoveries, meaning **only one backup copy is kept at a time**. -Use the `--storage-enable-backup-dir` flag to control this behavior: +Use the `--storage-backup-dir-enabled` flag to control this behavior: - `true` (default) - Old durability files are moved to `.old` directories - `false` - Old durability files are deleted immediately diff --git a/pages/clustering/replication/best-practices.mdx b/pages/clustering/replication/best-practices.mdx index 5e4f307bc..cd671628e 100644 --- a/pages/clustering/replication/best-practices.mdx +++ b/pages/clustering/replication/best-practices.mdx @@ -53,12 +53,25 @@ Invalid: Replication works **only** in the [in-memory transactional storage mode](/fundamentals/storage-memory-usage#in-memory-transactional-storage-mode-default). - -If you imported data using **in-memory analytical mode**, you must: - -1. Import the data -2. Switch the instance to **in-memory transactional mode** -3. Then configure replication +Writes performed in the [in-memory analytical storage +mode](/fundamentals/storage-memory-usage#in-memory-analytical-storage-mode) +never reach the WAL, so they cannot be replicated. + +If you want to import data using **in-memory analytical mode**, you must: + +1. `DROP REPLICA` every registered replica +2. Switch the MAIN to **in-memory analytical mode** +3. Import the data +4. Switch the MAIN back to **in-memory transactional mode** +5. `REGISTER REPLICA` the replicas again + +Step 4 writes a snapshot of the imported data and archives the superseded +durability files, so there is no need to run `CREATE SNAPSHOT` manually β€” and no +need to wipe a replica's data directory before registering it back, because +recovery detects that the imported data exists in no WAL file and ships the +snapshot instead. The same workflow for a high availability cluster is described +in [Bulk import in analytical +mode](/clustering/high-availability/analytical-import). ## Hardware requirements diff --git a/pages/clustering/replication/replication-commands-reference.mdx b/pages/clustering/replication/replication-commands-reference.mdx index 53ff3616e..e6e71297d 100644 --- a/pages/clustering/replication/replication-commands-reference.mdx +++ b/pages/clustering/replication/replication-commands-reference.mdx @@ -94,6 +94,18 @@ It should give you enough information to decide on which instance you can perfor ## Replica registration commands + + +`REGISTER REPLICA` and `DROP REPLICA` are refused while any database on the +instance is in the [`IN_MEMORY_ANALYTICAL` storage +mode](/fundamentals/storage-memory-usage#in-memory-analytical-storage-mode), +because analytical writes are not replicated. Switch every database back to +`IN_MEMORY_TRANSACTIONAL` and retry. For the analytical bulk import workflow, +see [storage mode +requirements](/clustering/replication/best-practices#storage-mode-requirements). + + + ### REGISTER REPLICA (SYNC) Registers a REPLICA instance with synchronous replication mode. diff --git a/pages/database-management/backup-and-restore.mdx b/pages/database-management/backup-and-restore.mdx index 07114a5f0..1787e3cc3 100644 --- a/pages/database-management/backup-and-restore.mdx +++ b/pages/database-management/backup-and-restore.mdx @@ -357,7 +357,7 @@ his protects your data if the newly loaded snapshot turns out to be corrupted. Configuration: -Use the `--storage-enable-backup-dir` flag to control this behavior: +Use the `--storage-backup-dir-enabled` flag to control this behavior: - `true` (default) - Old durability files are moved to `.old` directories - `false` - Old durability files are deleted immediately diff --git a/pages/database-management/configuration.mdx b/pages/database-management/configuration.mdx index 8166eeb3d..b7fd9b29a 100644 --- a/pages/database-management/configuration.mdx +++ b/pages/database-management/configuration.mdx @@ -489,7 +489,7 @@ in Memgraph. | `--storage-wal-enabled=true` | Controls whether the storage uses write-ahead-logging. To enable WAL, periodic snapshots must be enabled. | `[bool]` | | `--storage-wal-file-flush-every-n-tx=100000` | Issue a 'fsync' call after this amount of transactions are written to the WAL file. Set to 1 for fully synchronous operation. | `[uint64]` | | `--storage-wal-file-size-kib=20480` | Minimum file size of each WAL file. | `[uint64]` | -| `--storage-mode=IN_MEMORY_TRANSACTIONAL` | The storage mode Memgraph will run on startup. Can be IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL or ON_DISK_TRANSACTIONAL. | `[string]` | +| `--storage-mode=IN_MEMORY_TRANSACTIONAL` | The storage mode Memgraph will run on startup. Can be IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL or ON_DISK_TRANSACTIONAL. A data instance in a high availability cluster cannot start in IN_MEMORY_ANALYTICAL; it can only [switch to it at runtime](/clustering/high-availability/analytical-import). | `[string]` | | `--storage-enable-schema-metadata=false` | Facilitates the utilization of a specialized cache designed to store specific metadata related to the database. | `[bool]` | | `--storage-enable-edges-metadata=false` | Utilizes additional memory to store metadata related to edges. This metadata is used to speed up id based lookups on edges. | `[bool]` | | `--storage-light-edge=false` | Stores edges as lightweight objects to reduce memory footprint (saves ~24B per edge by removing the dedicated edge container). Implies `--storage-properties-on-edges=true`. Direct edge lookups by ID become slower; pair with `--storage-enable-edges-metadata=true` for such workloads. In-memory storage modes only. | `[bool]` | @@ -499,7 +499,7 @@ in Memgraph. | `--storage-property-store-compression-level=mid` | Controls property store compression level. Allowed values: low, mid, high | `[string]` | | `--storage-floating-point-resolution-bits=64` | Max bits for floating-point property storage. Allowed values: 16, 32, 64. Lower values save memory but reduce precision. | `[uint64]` | | `--storage-access-timeout-sec=1` | Storage access timeout in seconds. Used to fine-tune the responsiveness and guard against queries indefinitely waiting. Can also be changed at runtime via `SET DATABASE SETTING 'storage.access_timeout_sec' TO 'value'`. Valid range: [1, 1000000]. | `[uint64]` | -| `--storage-enable-backup-dir=true` | Controls whether `.old` directory will be used to store backup. | `[bool]` | +| `--storage-backup-dir-enabled=true` | Controls whether `.old` directory will be used to store backup. | `[bool]` | ### Streams diff --git a/pages/fundamentals/data-durability.mdx b/pages/fundamentals/data-durability.mdx index 704d09793..bc77e8c54 100644 --- a/pages/fundamentals/data-durability.mdx +++ b/pages/fundamentals/data-durability.mdx @@ -401,7 +401,7 @@ RECOVER SNAPSHOT "/path/to/good.snapshot"; On success, Memgraph clears the broken flag and the database resumes normal operation and background durability. The existing `RECOVER SNAPSHOT` behavior moves all prior/corrupt snapshots and WAL files to the `.old` directory (or -deletes them when [`--storage-enable-backup-dir`](/database-management/configuration) +deletes them when [`--storage-backup-dir-enabled`](/database-management/configuration) is off), leaving a clean single-snapshot directory. As a result, the database **recovers cleanly on the next restart and does not re-enter the broken state**. diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index 826fe6436..cf0c4b0c4 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -62,6 +62,11 @@ If some other transactions are running in your system, you will receive a warning message, so be sure to [set the log level at least to `WARNING`](/database-management/logs). +Switching to `IN_MEMORY_ANALYTICAL` is refused when replicas are registered, +because analytical writes are not replicated. See [In-memory analytical storage +mode](#in-memory-analytical-storage-mode) and [Bulk import in analytical +mode](/clustering/high-availability/analytical-import). + Switching from the in-memory storage mode to the on-disk storage mode is allowed when there is only one active session and the database is empty. As Memgraph Lab uses multiple sessions to run queries in parallel, it is currently impossible to @@ -223,23 +228,41 @@ SNAPSHOT;` Cypher query. In the analytical storage mode, WAL files and periodic snapshots are **not created**. -Before switching back to the in-memory transactional storage mode create a -snapshot manually. In the in-memory analytical storage mode, Memgraph guarantees -that creating a snapshot is **the only** transaction present in the system, and -all the other transactions will wait until the snapshot is created to ensure its -validity. Once Memgraph switches to the in-memory transactional mode, it will -restore data from the snapshot file and create a WAL for all new updates, if not -otherwise instructed by the [config +Switching back to the in-memory transactional storage mode writes a snapshot on +its own, synchronously and before the mode change completes, so there is no need +to create one manually first. If that snapshot cannot be written, the switch is +aborted and the query throws: the database stays in analytical mode with its +data and durability files unchanged, so you can fix the cause and retry. When +the snapshot succeeds, the superseded snapshots and WAL files are moved to the +`.old` directory β€” or deleted when +[`--storage-backup-dir-enabled`](/database-management/configuration) is set to +`false` β€” and WAL sequence numbering restarts from 0, because the new snapshot is +a new durability base rather than an increment on the old one. New updates are +written to a WAL again, if not otherwise instructed by the [config file](/configuration/configuration-settings#storage). +In the in-memory analytical storage mode, Memgraph guarantees that creating a +snapshot is **the only** transaction present in the system, and all the other +transactions will wait until the snapshot is created to ensure its validity. +Avoid taking a manual snapshot in the middle of an import you intend to finish +with a switch back to transactional mode, though: analytical writes do not +advance the durable timestamp, so such a snapshot is stamped with the +**pre-import** timestamp. These are some of the implications of not having ACID properties. But if you do not have write-heavy workload, and you want to run analytical queries that will not change the data, you can take advantage of the low memory costs of the analytical mode. -At the moment, the in-memory analytical storage mode **doesn't support -replication and high availability**. +Analytical writes are never appended to the WAL, so they **cannot be +replicated**. An instance can enter the analytical storage mode only when it is +the MAIN and has no registered replicas, and replicas can be registered or +unregistered only while every database is in transactional mode. Within those +rules, analytical mode can be used for a bulk import on a +[replication](/clustering/replication/best-practices#storage-mode-requirements) +or [high availability](/clustering/high-availability/analytical-import) cluster: +unregister the replicas, import, switch back to transactional mode and register +them again. ### On-disk transactional storage mode diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index e01e0d244..06b3a6b1d 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -160,6 +160,17 @@ guide. - `CREATE RANGE INDEX FOR ... ON ...` now works for nodes and relationships and creates Memgraph’s usual property index. [#4486](https://github.com/memgraph/memgraph/pull/4486) +- A data instance in a high availability cluster can now use the + `IN_MEMORY_ANALYTICAL` storage mode for a bulk import: unregister the + replicas, switch to analytical, import, switch back to + `IN_MEMORY_TRANSACTIONAL` and register the replicas again. The switch back + writes a snapshot of the imported data, archives the superseded durability + files and restarts WAL numbering, and replica recovery detects that the + imported data exists in no WAL file, so a re-registered replica catches up + from that snapshot without wiping its data directory. Starting a data instance + with `--storage-mode=IN_MEMORY_ANALYTICAL` remains forbidden. See [bulk import + in analytical mode](/clustering/high-availability/analytical-import). + [#4510](https://github.com/memgraph/memgraph/pull/4510) ### Lab v3.13.0 - September 9th, 2026 @@ -1221,7 +1232,7 @@ the `username()` and `roles()` built-in functions. These functions allow users to programmatically retrieve current authentication details, simplifying auditing and the development of dynamic, role-based logic within queries. [#3563](https://github.com/memgraph/memgraph/pull/3563) -- Added `--storage-enable-backup-dir` flag to control whether the `.old` +- Added `--storage-backup-dir-enabled` flag to control whether the `.old` directory is used for storing old durability files during potential data loss scenarios. When disabled, old durability files are deleted instead of backed up. [#3631](https://github.com/memgraph/memgraph/pull/3631) From cc9f104dd36f4a3fb5317f4b4b6302916c47e7ad Mon Sep 17 00:00:00 2001 From: David Ivekovic Date: Wed, 12 Aug 2026 11:54:17 +0200 Subject: [PATCH 11/19] docs: document property-value descriptions (#1721) Extend the server-side descriptions page with property-value descriptions: the PROPERTY

VALUE target, SET/DELETE examples, the new "property value" type and value column in SHOW DESCRIPTIONS, the description() function for resolving a value's label at query time, and an enum/lookup decoding use case. --- .../server-side-descriptions.mdx | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/pages/database-management/server-side-descriptions.mdx b/pages/database-management/server-side-descriptions.mdx index 1cd56770e..4fafdf48b 100644 --- a/pages/database-management/server-side-descriptions.mdx +++ b/pages/database-management/server-side-descriptions.mdx @@ -1,6 +1,6 @@ --- title: Server-side descriptions -description: Annotate labels, edge types, properties and databases with human-readable descriptions that are persisted by Memgraph and surfaced in SHOW SCHEMA INFO. +description: Annotate labels, edge types, properties, property values and databases with human-readable descriptions that are persisted by Memgraph and surfaced in SHOW SCHEMA INFO and the description() function. --- # Server-side descriptions @@ -9,6 +9,10 @@ Server-side descriptions are human-readable strings attached to schema elements - labels, edge types, properties and databases - that Memgraph stores durably and surfaces alongside the schema. +You can also describe individual property *values* - for example, decoding an +enum or lookup code such as `"1"` into `"Male"` - and read them back at query +time with the [`description()`](#resolve-a-value-with-description) function. + They are useful for documenting the meaning of nodes, edges and properties directly inside the database, so tools that consume `SHOW SCHEMA INFO` (such as LLM-based clients, GraphChat, MCP, text2cypher, or your own tooling) can pick @@ -33,11 +37,17 @@ You can attach a description to any of the following targets: | Edge type property | `EDGE TYPE PROPERTY :KNOWS(since)` | | Edge type pattern property | `EDGE TYPE PROPERTY (:Person)-[:KNOWS]->(:Person)(since)` | | Property (global) | `PROPERTY age` | +| Property value | `PROPERTY gender VALUE "1"` | | Database | `DATABASE memgraph` | Multi-label combinations are matched exactly; setting a description on `:Person:Student` does not affect nodes that only carry `:Person`. +A property-value description is keyed on the exact value: `PROPERTY gender VALUE "1"` +describes only the value `"1"` of `gender`, independent of any global +`PROPERTY gender` description. The value can be any literal (a string, number or +boolean). + ## Set a description Use `SET DESCRIPTION ON ""`: @@ -54,6 +64,8 @@ SET DESCRIPTION ON LABEL PROPERTY :Person(name) "Full name"; SET DESCRIPTION ON EDGE TYPE PROPERTY :KNOWS(since) "Year they met"; SET DESCRIPTION ON EDGE TYPE PROPERTY (:Person)-[:KNOWS]->(:Person)(since) "Year they met (pattern)"; SET DESCRIPTION ON PROPERTY age "Age in years"; +SET DESCRIPTION ON PROPERTY gender VALUE "1" "Male"; +SET DESCRIPTION ON PROPERTY gender VALUE "2" "Female"; SET DESCRIPTION ON DATABASE memgraph "Main graph database"; ``` @@ -70,6 +82,7 @@ DELETE DESCRIPTION ON LABEL :Person; DELETE DESCRIPTION ON EDGE TYPE (:Person)-[:KNOWS]->(:Person); DELETE DESCRIPTION ON LABEL PROPERTY :Person(name); DELETE DESCRIPTION ON PROPERTY age; +DELETE DESCRIPTION ON PROPERTY gender VALUE "1"; DELETE DESCRIPTION ON DATABASE memgraph; ``` @@ -84,18 +97,56 @@ SHOW DESCRIPTIONS; Result columns: - `type` - kind of target. One of `"label"`, `"edge type"`, `"label property"`, - `"edge type property"`, `"property"`, or `"database"`. Edge-type-pattern - targets share the `"edge type"` / `"edge type property"` value with their - global counterparts and are distinguished by the populated + `"edge type property"`, `"property"`, `"property value"`, or `"database"`. + Edge-type-pattern targets share the `"edge type"` / `"edge type property"` + value with their global counterparts and are distinguished by the populated `start_node_labels` and `end_node_labels` columns. - `label` - label or label combination, when applicable. - `start_node_labels` - source labels, for edge type patterns. - `end_node_labels` - destination labels, for edge type patterns. - `property` - property key, when applicable. +- `value` - the described value, for `"property value"` rows. - `description` - the stored text. Columns that don't apply to a given row are returned as `Null`. +## Resolve a value with `description()` + +Property-value descriptions are read back at query time with the `description()` +function, which maps a value to the description set for it: + +```opencypher +description(property_name, value) +``` + +- `property_name` - the property key, as a string. +- `value` - the value to look up, typically a stored property. + +It returns the description for that property/value pair, or `Null` if none is +set (or if `value` is `Null`). + +For example, after: + +```opencypher +SET DESCRIPTION ON PROPERTY gender VALUE "1" "Male"; +SET DESCRIPTION ON PROPERTY gender VALUE "2" "Female"; +``` + +stored codes can be decoded into labels: + +```opencypher +MATCH (p:Person) +RETURN p.name, description("gender", p.gender) AS gender; +``` + +| p.name | gender | +|---------|------------| +| "Alice" | "Male" | +| "Bob" | "Female" | +| "Carol" | `Null` | + +`Carol`'s `gender` has no matching description, so it resolves to `Null`. + ## Descriptions in `SHOW SCHEMA INFO` When [run-time schema tracking](/querying/schema) is enabled, `SHOW SCHEMA INFO` @@ -178,3 +229,17 @@ SET DESCRIPTION ON LABEL PROPERTY :Sensor(temperature) "Reading in degrees Celsi SET DESCRIPTION ON LABEL PROPERTY :Order(amount) "Total in cents, in the order's currency"; SET DESCRIPTION ON EDGE TYPE :PAID_WITH "Links an order to the payment method actually charged"; ``` + +### Decoding enum or lookup values + +Property-value descriptions turn opaque codes into readable labels without a +join or a separate lookup table. Describe each code once, then resolve it at +query time with `description()`: + +```opencypher +SET DESCRIPTION ON PROPERTY status VALUE "A" "Active"; +SET DESCRIPTION ON PROPERTY status VALUE "C" "Closed"; + +MATCH (a:Account) +RETURN a.id, description("status", a.status) AS status; +``` From e1dedf2a53e6ca6a825577e9e56cf51629734608 Mon Sep 17 00:00:00 2001 From: Gareth Andrew Lloyd Date: Wed, 12 Aug 2026 10:59:45 +0100 Subject: [PATCH 12/19] docs: document --storage-snapshot-writeback-window-mib (#1720) Snapshots are written out in bounded chunks so they don't take memory away from the workload running alongside them. Document the flag that sets the chunk size, including that it applies per snapshot thread and so multiplies by --storage-snapshot-thread-count under parallel snapshot creation. The tuning guidance deliberately avoids predicting which direction helps: the effect depends on the hardware, operating system and workload, so it points at measurement rather than an expected result. --- pages/database-management/configuration.mdx | 1 + pages/fundamentals/data-durability.mdx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/pages/database-management/configuration.mdx b/pages/database-management/configuration.mdx index b7fd9b29a..0dcb3b760 100644 --- a/pages/database-management/configuration.mdx +++ b/pages/database-management/configuration.mdx @@ -486,6 +486,7 @@ in Memgraph. | `--storage-snapshot-retention-count=3` | The number of snapshots that should always be kept. | `[uint64]` | | `--storage-parallel-snapshot-creation=false` | Controls whether the snapshot creation can be done in a multi-threaded fashion. | `[bool]` | | `--storage-snapshot-thread-count` | The number of threads used to create snapshots. Defaults to using system's maximum thread count. | `[uint64]` | +| `--storage-snapshot-writeback-window-mib` | How large a chunk of a snapshot is written to disk at a time, in MiB. Defaults to 32. Set to 0 to disable. See [limiting the impact of snapshots on queries](/fundamentals/data-durability#limiting-the-impact-of-snapshots-on-queries). | `[uint64]` | | `--storage-wal-enabled=true` | Controls whether the storage uses write-ahead-logging. To enable WAL, periodic snapshots must be enabled. | `[bool]` | | `--storage-wal-file-flush-every-n-tx=100000` | Issue a 'fsync' call after this amount of transactions are written to the WAL file. Set to 1 for fully synchronous operation. | `[uint64]` | | `--storage-wal-file-size-kib=20480` | Minimum file size of each WAL file. | `[uint64]` | diff --git a/pages/fundamentals/data-durability.mdx b/pages/fundamentals/data-durability.mdx index bc77e8c54..449dfc644 100644 --- a/pages/fundamentals/data-durability.mdx +++ b/pages/fundamentals/data-durability.mdx @@ -295,6 +295,24 @@ test, remove the file. You can also monitor real-time disk utilization during snapshot creation using tools like `iostat`, `iotop`, or `dstat`. +### Limiting the impact of snapshots on queries + +A snapshot writes the whole dataset to disk, and on larger datasets that can +slow down the queries running at the same time. Memgraph therefore writes +snapshots out in small chunks, so a snapshot in progress doesn't take memory +away from your workload. + +The chunk size is set with `--storage-snapshot-writeback-window-mib` and +defaults to 32 MiB. Each snapshot thread uses its own chunk, so with +`--storage-parallel-snapshot-creation=true` the total is the chunk size times +`--storage-snapshot-thread-count`. + +The default suits most deployments. Tuning it trades off snapshot duration +against the performance of queries running at the same time, but the balance +depends on your hardware, your operating system and your workload, so treat any +change as something to measure on your own deployment. Setting it to `0` turns +the behavior off and lets the operating system decide. + ## Recovery failure handling By default, if a database fails durability recovery on startup β€” because of a From 5262ca5f3c78cd562e03bef4b8588753ce1c4146 Mon Sep 17 00:00:00 2001 From: David Ivekovic Date: Wed, 12 Aug 2026 12:05:23 +0200 Subject: [PATCH 13/19] docs: document RBAC for text and vector search (#1719) Add an "Access control" section to the text-search and vector-search pages explaining how fine-grained access control filters results: a node or relationship surfaces only if the caller can read it (labels/type, and both endpoints for relationships) and the matched/indexed property. Notes the per-procedure property gating (search vs search_all/regex), silent dropping, the aggregate limitation, and wildcard-index per-result filtering. --- pages/querying/text-search.mdx | 65 ++++++++++++++++++++++++++++++++ pages/querying/vector-search.mdx | 46 ++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/pages/querying/text-search.mdx b/pages/querying/text-search.mdx index bad2c236b..a74861ad7 100644 --- a/pages/querying/text-search.mdx +++ b/pages/querying/text-search.mdx @@ -616,6 +616,71 @@ Result: +-------------------------------+ ``` +## Access control Enterprise + +When [fine-grained access control](/database-management/authentication-and-authorization/role-based-access-control#fine-grained-access-control) +is active for the user running the query, text search respects their read +permissions: a match is returned only if the user could read it with a normal +query. Content the user is not allowed to read is never revealed through the +search results or their relevance scores. + +A node or relationship appears in the results only when **both** of these hold: + +- **The node or relationship is readable.** The user has `READ` on its + [labels](/database-management/authentication-and-authorization/role-based-access-control#label-based-access-control) + β€” or, for a relationship, on its + [type](/database-management/authentication-and-authorization/role-based-access-control#relationship-permissions) + and on both of its endpoint nodes. Permissions are evaluated against the + entity's *actual* labels, so a `DENY` on any of them removes it from the + results. +- **The matched property is readable.** Which properties must be readable + depends on the procedure: + - `text_search.search` and `text_search.search_edges` search a property named + in the query (for example `data.name:Alice`), so only that + [property](/database-management/authentication-and-authorization/role-based-access-control#property-based-access-control) + must be readable. The match surfaces even if the entity has other properties + the user cannot read. + - `text_search.search_all`, `text_search.regex_search` and their `_edges` + variants search across every indexed property, so **all** indexed text + properties the entity has must be readable β€” if even one is denied, the + entity is dropped, because the match could have come from it. + +A hit whose match depends on a property the user cannot read is **silently +dropped**: the results are simply empty, just as a +[denied property](/database-management/authentication-and-authorization/role-based-access-control#how-denied-properties-behave) +reads back as `Null` in a normal query. No error is raised. + +For example, with a text index on `:Employee` and an analyst who can read +employees and their properties, except `ssn`: + +```cypher +GRANT READ ON NODES CONTAINING LABELS :Employee TO analyst; +GRANT READ {*} ON NODES CONTAINING LABELS :Employee TO analyst; +DENY READ {ssn} ON NODES CONTAINING LABELS :Employee TO analyst; +``` + +- `CALL text_search.search('employees', 'data.name:Alice')` returns matching + employees β€” `name` is readable. +- `CALL text_search.search('employees', 'data.ssn:123456789')` returns nothing β€” + the queried property is denied. +- `CALL text_search.search_all('employees', 'Alice')` returns nothing for an + employee whose indexed `ssn` is set, because the match could have come from the + denied property. + + +`text_search.aggregate` and `text_search.aggregate_edges` run inside the search +engine and cannot filter individual results, so they cannot apply per-row +permissions. When the caller has a fine-grained restriction on the index's +properties, the aggregation returns an error message instead of a result β€” use +the search procedures above when you need permission-aware results. + + + +These checks apply only when fine-grained access control is active for the user. +An unrestricted user (for example an administrator) and Memgraph Community +edition see all matches. + + ## Drop text index Text indices are dropped with the `DROP TEXT INDEX` command. You need to give the name of the index to be deleted. diff --git a/pages/querying/vector-search.mdx b/pages/querying/vector-search.mdx index 8d0e525d0..66c4e0e5c 100644 --- a/pages/querying/vector-search.mdx +++ b/pages/querying/vector-search.mdx @@ -307,6 +307,52 @@ Alternative options, such as `f16` for lower memory usage, allow you to fine-tun | `i16` | 16-bit signed integer. | | `i8` | 8-bit signed integer. | +## Access control Enterprise + +When [fine-grained access control](/database-management/authentication-and-authorization/role-based-access-control#fine-grained-access-control) +is active for the user running the query, vector search respects their read +permissions: `vector_search.search` and `vector_search.search_edges` return a +node or relationship only if the user could read it with a normal query. + +A result is included only when **both** of these hold: + +- **The node or relationship is readable.** The user has `READ` on its + [labels](/database-management/authentication-and-authorization/role-based-access-control#label-based-access-control) + β€” or, for a relationship, on its + [type](/database-management/authentication-and-authorization/role-based-access-control#relationship-permissions) + and on both endpoint nodes. Permissions are evaluated against the entity's + *actual* labels, so a `DENY` on any of them removes it. +- **The indexed property is readable.** The user must be able to read the + [property](/database-management/authentication-and-authorization/role-based-access-control#property-based-access-control) + the index is built on (the embedding). If it is denied, the result is dropped β€” + otherwise the match and its similarity score would reveal the vector. + +Results the user is not allowed to see are **silently dropped** (no error is +raised), so a search may return fewer than the requested number of results. For +an index that spans several labels or edge types β€” including a +[wildcard index](/querying/vector-search#index-on-multiple-labels-or-edge-types) β€” +this check runs per result, so one index returns only the entities the user is +permitted to read. + +For example, with a wildcard vector index `docs` on `(embedding)` and a reader +who may see public documents but not confidential ones: + +```cypher +GRANT READ ON NODES CONTAINING LABELS :Public TO reader; +DENY READ ON NODES CONTAINING LABELS :Confidential TO reader; +GRANT READ {*} ON NODES CONTAINING LABELS * TO reader; +``` + +`CALL vector_search.search('docs', 5, [0.1, 0.2])` returns only the `:Public` +documents among the nearest neighbours; `:Confidential` ones are dropped even if +they are closer. + + +These checks apply only when fine-grained access control is active for the user. +An unrestricted user (for example an administrator) and Memgraph Community +edition see all matches. + + ## Monitor vector index memory Memgraph tracks vector index memory separately from the rest of the graph data. You can inspect both from `SHOW STORAGE INFO`: From b50bbc0f62c475bcac38adfbbcc9b155ec689f9b Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 12:16:56 +0200 Subject: [PATCH 14/19] Document TERMINATE TRANSACTIONS "*" wildcard (#1716) Covers memgraph/memgraph#4534: - new "Terminate all transactions" section with scope, authorization, self-exclusion, ordering and no-mixing rules - breaking change: transaction ids must parse in full - unauthorized matches now report killed: false - privileges table row and v3.13.0 release notes entries Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- .../query-privileges.mdx | 1 + pages/fundamentals/transactions.mdx | 80 ++++++++++++++++++- pages/release-notes.mdx | 15 ++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/pages/database-management/authentication-and-authorization/query-privileges.mdx b/pages/database-management/authentication-and-authorization/query-privileges.mdx index 7fb7ab614..a46738b84 100644 --- a/pages/database-management/authentication-and-authorization/query-privileges.mdx +++ b/pages/database-management/authentication-and-authorization/query-privileges.mdx @@ -146,6 +146,7 @@ Memgraph's privilege system controls access to various database operations throu | `SHOW VERSION` | `STATS` | `SHOW VERSION` | | `SHOW TRANSACTIONS` | `TRANSACTION_MANAGEMENT` | `SHOW TRANSACTIONS` | | `TERMINATE TRANSACTIONS` | `TRANSACTION_MANAGEMENT` | `TERMINATE TRANSACTIONS 'transaction_id'` | +| `TERMINATE TRANSACTIONS "*"` | `TRANSACTION_MANAGEMENT` | `TERMINATE TRANSACTIONS "*"` terminates every transaction the user may terminate. Without the privilege, only the user's own transactions are terminated. | | `RELOAD BOLT_SERVER TLS` | `RELOAD_TLS` | `RELOAD BOLT_SERVER TLS` | | `RELOAD INTRA_CLUSTER TLS` | `RELOAD_TLS` | `RELOAD INTRA_CLUSTER TLS` | diff --git a/pages/fundamentals/transactions.mdx b/pages/fundamentals/transactions.mdx index 188764e57..537c0f434 100644 --- a/pages/fundamentals/transactions.mdx +++ b/pages/fundamentals/transactions.mdx @@ -204,10 +204,11 @@ synthetic `gc` row may appear at once, distinguished by `trigger`. -Synthetic rows cannot be terminated. Passing `"snapshot"` or `"gc"` to -`TERMINATE TRANSACTIONS` has no effect β€” background snapshot creation and -garbage collection run outside the normal transaction lifecycle and cannot be -interrupted via Cypher. +Synthetic rows cannot be terminated. Background snapshot creation and garbage +collection run outside the normal transaction lifecycle and cannot be +interrupted via Cypher. Passing `"snapshot"` or `"gc"` to `TERMINATE +TRANSACTIONS` raises an error because they are not valid transaction ids, and +`TERMINATE TRANSACTIONS "*"` skips them. Because snapshot and garbage-collection rows always have `status` `"running"`, @@ -285,6 +286,77 @@ The `TERMINATE TRANSACTIONS` query signalizes to the thread executing the transaction that it should stop the execution. No violent interruption will happen, and the whole system will stay in a consistent state. +The result has two columns, `transaction_id` and `killed`. A transaction +reports `killed: true` only if it was actually found, the caller was allowed to +terminate it, and it was still running. Ids that do not match a running +transaction, and matches the caller is not authorized to terminate, both report +`killed: false` β€” an unauthorized match is indistinguishable from a missing id, +so the query never reveals that somebody else's transaction exists. + + +**Breaking change in Memgraph 3.13**: transaction ids must now parse as a whole +number. Previously an id with trailing characters (for example +`TERMINATE TRANSACTIONS "9223372036854794885abc"`) silently terminated the +transaction matching the numeric prefix, and a completely unparseable id was +reported back as a termination attempt on `18446744073709551615`. Both cases +now raise an error instead. + +In the same release, naming a transaction you are not authorized to terminate +reports `killed: false` instead of `true`. Previously such a query claimed a +kill that never happened. + + +#### Terminate all transactions + +Instead of copying ids out of `SHOW TRANSACTIONS` one at a time, you can +terminate everything at once with the `"*"` wildcard: + +```cypher +TERMINATE TRANSACTIONS "*"; +``` + +This terminates every transaction the caller is authorized to terminate, +following the same rules as the id list form: + +- **Scope**: all transactions visible through `SHOW TRANSACTIONS`, across all + databases β€” not just the caller's current database. +- **Authorization**: a transaction is terminated if the caller owns it, or holds + the **TRANSACTION_MANAGEMENT** privilege on the database that transaction is + running on. A user without the privilege terminates only its own + transactions; the query does not fail, it simply returns fewer rows. + Transactions the caller may not terminate are absent from the output rather + than reported as `killed: false`, so no transaction id is leaked. +- **The caller's own transaction is skipped**, so the session issuing the sweep + survives it and can read back the result. +- **Output**: the usual `transaction_id` and `killed` columns, one row per + terminated transaction ordered by ascending transaction id (oldest first), + and zero rows if nothing matched. +- Transactions that are already committing or aborting are skipped, the same as + with the id list form. + +The wildcard must be the only argument. Mixing it with ids, such as +`TERMINATE TRANSACTIONS "*", "9223372036854794885"`, raises an error. + +A parameterized id is treated exactly like a literal one, so running +`TERMINATE TRANSACTIONS $id` with `$id = "*"` also terminates everything. + + +System transactions (for example an in-flight `CREATE DATABASE` or `GRANT`) are +reported as terminated but run to completion β€” they are not abortable. This +applies to the id list form as well. + + +```copy=false +memgraph> TERMINATE TRANSACTIONS "*"; ++-----------------------+-----------------------+ +| transaction_id | killed | ++-----------------------+-----------------------+ +| "9223372036854794885" | true | +| "9223372036854794891" | true | ++-----------------------+-----------------------+ +2 rows in set (round trip in 0.001 sec) +``` + ### Terminate custom procedures diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 06b3a6b1d..252455d4b 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -58,6 +58,11 @@ guide. you need the previous Python behavior, or update consumers for the new structured / ISO-8601 JSON forms. [#4443](https://github.com/memgraph/memgraph/pull/4443) +- `TERMINATE TRANSACTIONS` now requires transaction ids to parse in full. Ids + with trailing characters previously terminated the transaction matching the + numeric prefix, and unparseable ids were reported back as an attempt on + `18446744073709551615`; both now raise an error. + [#4534](https://github.com/memgraph/memgraph/pull/4534) {

🐞 Bug fixes

} @@ -102,6 +107,10 @@ guide. - Fixed a crash when multiple sessions printed query plans at the same time (for example concurrent `EXPLAIN` / `PROFILE`, or query-plan logging). [#4489](https://github.com/memgraph/memgraph/pull/4489) +- `TERMINATE TRANSACTIONS` no longer reports `killed: true` for a transaction + the caller is not authorized to terminate. Such a match now reports + `killed: false`, indistinguishable from an id that does not exist. + [#4534](https://github.com/memgraph/memgraph/pull/4534) {

πŸ› οΈ Improvements

} @@ -160,6 +169,12 @@ guide. - `CREATE RANGE INDEX FOR ... ON ...` now works for nodes and relationships and creates Memgraph’s usual property index. [#4486](https://github.com/memgraph/memgraph/pull/4486) +- Added `TERMINATE TRANSACTIONS "*"`, which terminates every transaction the + caller is authorized to terminate across all databases, instead of naming ids + one by one. The caller's own transaction is skipped, transactions it may not + terminate are omitted from the result, and rows come back ordered by + ascending transaction id. The wildcard cannot be combined with ids. + [#4534](https://github.com/memgraph/memgraph/pull/4534) - A data instance in a high availability cluster can now use the `IN_MEMORY_ANALYTICAL` storage mode for a bulk import: unregister the replicas, switch to analytical, import, switch back to From a928335b6ce1420e9a1e8cf829c9ec8076da891a Mon Sep 17 00:00:00 2001 From: colinbarry Date: Wed, 12 Aug 2026 11:23:00 +0100 Subject: [PATCH 15/19] docs: Add global vertex-property index docs (#1707) * doc: Add docs for global vertex-property indices * refactor: Tidy grammar a little --- pages/database-management/monitoring.mdx | 2 + pages/database-management/server-stats.mdx | 2 + pages/fundamentals/indexes.mdx | 66 ++++++++++++++++++++-- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/pages/database-management/monitoring.mdx b/pages/database-management/monitoring.mdx index c596a0d3d..e967f5d25 100644 --- a/pages/database-management/monitoring.mdx +++ b/pages/database-management/monitoring.mdx @@ -264,6 +264,7 @@ JSON endpoint and `SHOW METRICS INFO` use different names β€” see | memgraph\_active\_edge\_type\_indices | Gauge | Number of active edge-type indexes. | | memgraph\_active\_edge\_type\_property\_indices | Gauge | Number of active edge-type-property indexes. | | memgraph\_active\_edge\_property\_indices | Gauge | Number of active edge-property indexes. | + | memgraph\_active\_vertex\_property\_indices | Gauge | Number of active vertex-property indexes. | | memgraph\_active\_point\_indices | Gauge | Number of active point indexes. | | memgraph\_active\_text\_indices | Gauge | Number of active text indexes on vertices. | | memgraph\_active\_text\_edge\_indices | Gauge | Number of active text indexes on edges. | @@ -321,6 +322,7 @@ used. | memgraph\_scan\_all\_by\_edge\_property\_value\_operator\_total | Counter | | memgraph\_scan\_all\_by\_edge\_property\_range\_operator\_total | Counter | | memgraph\_scan\_all\_by\_edge\_id\_operator\_total | Counter | + | memgraph\_scan\_all\_by\_vertex\_property\_operator\_total | Counter | | memgraph\_scan\_all\_by\_point\_distance\_operator\_total | Counter | | memgraph\_scan\_all\_by\_point\_withinbbox\_operator\_total | Counter | | memgraph\_expand\_operator\_total | Counter | diff --git a/pages/database-management/server-stats.mdx b/pages/database-management/server-stats.mdx index 86bbec23e..51a8c2c19 100644 --- a/pages/database-management/server-stats.mdx +++ b/pages/database-management/server-stats.mdx @@ -230,6 +230,7 @@ are reported in microseconds. | "ActiveEdgeTypePropertyIndices" | "Index" | "Gauge" | 0 | | "ActiveLabelIndices" | "Index" | "Gauge" | 6 | | "ActiveLabelPropertyIndices" | "Index" | "Gauge" | 18 | +| "ActiveVertexPropertyIndices" | "Index" | "Gauge" | 0 | | "ActivePointIndices" | "Index" | "Gauge" | 0 | | "ActiveTextIndices" | "Index" | "Gauge" | 0 | | "ActiveTextEdgeIndices" | "Index" | "Gauge" | 0 | @@ -293,6 +294,7 @@ are reported in microseconds. | "ScanAllByIdOperator" | "Operator" | "Counter" | 0 | | "ScanAllByLabelOperator" | "Operator" | "Counter" | 0 | | "ScanAllByLabelPropertiesOperator" | "Operator" | "Counter" | 0 | +| "ScanAllByVertexPropertyOperator" | "Operator" | "Counter" | 0 | | "ScanAllByPointDistanceOperator" | "Operator" | "Counter" | 0 | | "ScanAllByPointWithinbboxOperator" | "Operator" | "Counter" | 0 | | "ScanAllOperator" | "Operator" | "Counter" | 0 | diff --git a/pages/fundamentals/indexes.mdx b/pages/fundamentals/indexes.mdx index 63e5c9d58..ab20bfbf6 100644 --- a/pages/fundamentals/indexes.mdx +++ b/pages/fundamentals/indexes.mdx @@ -477,6 +477,53 @@ not yet supported. +### Global vertex property index + + + +This index supports non-blocking creation: reads continue without interruption, +while writes are briefly paused. For more information, see the [concurrent index +creation](#concurrent-index-creation). + + + +A label-property index requires you to know the label up front. But sometimes +you just want to find a node by a property, say `uuid`, regardless of what +labels the node has. Global vertex-property indices allow you to do exactly +this. For example, to create an index on all nodes with a `uuid` property: + +```cypher +CREATE GLOBAL INDEX ON :(uuid); +``` + +Once created, queries that search by that property can use the index +automatically: + +```cypher +MATCH (n) WHERE n.uuid = "abc-123" RETURN n; +MATCH (n {uuid: "abc-123"}) RETURN n; +``` + +The planner also considers this index as a fallback when a query specifies a +label but no matching label-property index exists. For example, if there is no +index on `:Person(uuid)` but a global index on `uuid` exists, +`MATCH (n:Person {uuid: "abc-123"}) RETURN n` can use the global index and +filter by label afterwards. + + + +When both a label-property index and a global vertex property index could serve +a query, the planner picks whichever has the lower estimated cardinality. In +practice this usually means the label-property index wins, since it's scoped to +a single label. + + + +To drop the index: +```cypher +DROP GLOBAL INDEX ON :(uuid); +``` + ### Edge-type index @@ -696,7 +743,8 @@ memgraph> MATCH (n:Person) WHERE n.name =~ ".*an$" RETURN n.name; ## Show created indexes To see all the information on the label, label-property, edge-type, edge-type -property, point indexes and vector indexes, run the following query: +property, global vertex property, global edge property, point indexes and vector +indexes, run the following query: ```cypher SHOW INDEX INFO; @@ -739,6 +787,10 @@ DROP INDEX ON :Label(property); DROP INDEX ON :Label(property1, property2); ``` +```cypher +DROP GLOBAL INDEX ON :(property_name); +``` + ```cypher DROP EDGE INDEX ON :EDGE_TYPE; ``` @@ -760,8 +812,8 @@ DROP POINT INDEX ON :Label(property); The `DROP ALL INDEXES` clause allows you to delete all indices in your database in a single operation. This includes all types of indices: label indices, label-property indices, edge type indices, edge type-property indices, global -edge indices, point indices, text indices, vector indices, and vector edge -indices. +vertex property indices, global edge property indices, point indices, text +indices, vector indices, and vector edge indices. ```cypher DROP ALL INDEXES; @@ -956,6 +1008,10 @@ USING INDEX :Label(property) ...; USING INDEX :Label(property1, property2) ...; ``` +```cypher +USING INDEX :(property) ...; +``` + It is also possible to specify multiple hints separated with comma. In that case, the planner will apply the first hint that is applicable for a given match. @@ -1008,8 +1064,8 @@ b_1$, or $a_1 = b_1$ and $a_2 < b_2$. Memgraph supports **(almost) fully concurrent index creation** for all skiplist-based indices, including label, label-property, composite, edge-type, -edge-type property, and global edge property, with minimal impact on -performance. +edge-type property, global edge property, and global vertex property, with +minimal impact on performance. The three-phase implementation begins with a brief **registration phase** that requires `READ ONLY` access. This ensures that all pending write transactions From b1452d2af66dbd6258f35f341d03357b4e1eb55b Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 12:38:26 +0200 Subject: [PATCH 16/19] docs: SHOW ROUTING TABLE query (#1710) Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- .../ha-commands-reference.mdx | 54 +++++++++++++++++++ .../how-high-availability-works.mdx | 2 +- ...rying-the-cluster-in-high-availability.mdx | 16 ++++++ .../query-privileges.mdx | 1 + pages/release-notes.mdx | 5 ++ 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/pages/clustering/high-availability/ha-commands-reference.mdx b/pages/clustering/high-availability/ha-commands-reference.mdx index d91a1976b..572c1af60 100644 --- a/pages/clustering/high-availability/ha-commands-reference.mdx +++ b/pages/clustering/high-availability/ha-commands-reference.mdx @@ -429,6 +429,60 @@ SHOW REPLICATION LAG; - Useful during manual failover to evaluate risk of data loss. +### `SHOW ROUTING TABLE` + +Shows the routing table that a coordinator hands out to +[`bolt+routing`](/clustering/high-availability/querying-the-cluster-in-high-availability) +clients for the default database. + +```cypher +SHOW ROUTING TABLE; +``` + +{

Output includes

} + +Each row contains a `role` and the list of Bolt `servers` serving that role: + +| `role` | `servers` | +| ------- | ------------------------------------------------------------------ | +| `WRITE` | Bolt endpoint of the current MAIN. | +| `READ` | Bolt endpoints of all REPLICAs, plus MAIN if [`enabled_reads_on_main`](#enabled_reads_on_main) is set to `true`. | +| `ROUTE` | Bolt endpoints of all coordinators. | + +Example output on a cluster with three coordinators, one MAIN and two REPLICAs: + +```plaintext ++---------+------------------------------------------------------------------+ +| role | servers | ++---------+------------------------------------------------------------------+ +| "WRITE" | ["localhost:7687"] | +| "READ" | ["localhost:7688", "localhost:7689"] | +| "ROUTE" | ["localhost:7690", "localhost:7691", "localhost:7692"] | ++---------+------------------------------------------------------------------+ +``` + +{

Behavior

} + +- The query can only be run on a coordinator. Running it on a data instance + fails with `Only coordinator can run SHOW ROUTING TABLE query.` +- The query is always answered from the leader's state, so every coordinator + returns the same routing table. If the leader cannot be contacted, an empty + routing table is returned. +- Roles with no servers are omitted. For example, if no data instance is + registered yet, only the `ROUTE` row is returned. +- The routing table is reported for the default database, which is the same + database `bolt+routing` clients are routed to. + +{

Implications

} + +- Useful for verifying which instance clients will send writes to, and which + instances they can read from, without inspecting driver internals. +- Because drivers cache the routing table for up to 5 minutes, the output of + this query can differ from what a connected client is currently using. See + [routing table TTL and refresh + behavior](/clustering/high-availability/querying-the-cluster-in-high-availability#routing-table-ttl-and-refresh-behavior). + + ## Coordinator runtime settings Coordinator runtime settings are Raft-replicated and can be changed on a live diff --git a/pages/clustering/high-availability/how-high-availability-works.mdx b/pages/clustering/high-availability/how-high-availability-works.mdx index 53056a487..55845735e 100644 --- a/pages/clustering/high-availability/how-high-availability-works.mdx +++ b/pages/clustering/high-availability/how-high-availability-works.mdx @@ -169,7 +169,7 @@ Below is a cleaned-up categorization. | `DemoteInstanceRpc` | Follower requests demoting an instance. | Sent by a follower coordinator to the leader coordinator when a user executes `DEMOTE INSTANCE` through the follower. | | `UpdateConfigRpc` | Follower requests updating config. | Sent by a follower coordinator to the leader coordinator when a user executes `UPDATE CONFIG` through the follower. | | `ForceResetRpc` | Follower requests resetting cluster state. | Sent by a follower coordinator to the leader coordinator when a user executes `FORCE RESET` through the follower. | -| `GetRoutingTableRpc` | Follower requests a routing table. | Sent by a follower coordinator to the leader coordinator when a user connects using `bolt+routing` through the follower. | +| `GetRoutingTableRpc` | Follower requests a routing table. | Sent by a follower coordinator to the leader coordinator when a user connects using `bolt+routing` or executes `SHOW ROUTING TABLE` through the follower. | | `CoordReplicationLagRpc` | Follower requests replication lag info. | Sent by a follower coordinator to the leader coordinator when a user executes `SHOW REPLICATION LAG` through the follower. | diff --git a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx index 74eaa3b51..ebb6e6789 100644 --- a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx +++ b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx @@ -75,6 +75,22 @@ This ensures: - **Transparency:** Clients work seamlessly whether they connect to leaders or followers. +### Inspecting the routing table + +The routing table a coordinator hands out can be inspected manually with the +[`SHOW ROUTING TABLE`](/clustering/high-availability/ha-commands-reference#show-routing-table) +query: + +```cypher +SHOW ROUTING TABLE; +``` + +It returns one row per role (`WRITE`, `READ`, `ROUTE`) with the Bolt endpoints +serving that role, for the default database. The query can only be run on a +coordinator, and it is always answered from the leader's state, so every +coordinator returns the same result. If the leader cannot be contacted, an empty +routing table is returned. + ### Routing table TTL and refresh behavior Because routing is entirely client-side, the driver caches the routing table and diff --git a/pages/database-management/authentication-and-authorization/query-privileges.mdx b/pages/database-management/authentication-and-authorization/query-privileges.mdx index a46738b84..0e707cd3f 100644 --- a/pages/database-management/authentication-and-authorization/query-privileges.mdx +++ b/pages/database-management/authentication-and-authorization/query-privileges.mdx @@ -196,6 +196,7 @@ Memgraph's privilege system controls access to various database operations throu |------------|-------------------|---------| | `COORDINATOR` operations | `COORDINATOR` | Various coordinator commands. | | `SHOW COORDINATOR SETTINGS` | `COORDINATOR` | `SHOW COORDINATOR SETTINGS` | +| `SHOW ROUTING TABLE` | `COORDINATOR` | `SHOW ROUTING TABLE` | ## Schema information diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 252455d4b..cbc629c32 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -169,6 +169,11 @@ guide. - `CREATE RANGE INDEX FOR ... ON ...` now works for nodes and relationships and creates Memgraph’s usual property index. [#4486](https://github.com/memgraph/memgraph/pull/4486) +- Added the `SHOW ROUTING TABLE` query, which shows the routing table a + coordinator hands out to `bolt+routing` clients as `WRITE`, `READ` and `ROUTE` + rows. It can only be run on a coordinator, is always answered from the + leader's state, and returns an empty table if the leader cannot be contacted. + [#4502](https://github.com/memgraph/memgraph/pull/4502) - Added `TERMINATE TRANSACTIONS "*"`, which terminates every transaction the caller is authorized to terminate across all databases, instead of naming ids one by one. The caller's own transaction is skipped, transactions it may not From 7919695e84bde9d3851e5d4806b74aa6975ccb21 Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 13:57:32 +0200 Subject: [PATCH 17/19] docs: strongly consistent SHOW INSTANCES and request forwarding (#1709) Document memgraph/memgraph#4492: - SHOW INSTANCES is now always answered by the leader coordinator. A follower that cannot reach the leader returns an empty result set with a LeaderNotReachable warning instead of falling back to its local Raft state with "unknown" health. A down data instance now reports the role recorded in the Raft log. - YIELD LEADERSHIP and SHOW COORDINATOR SETTINGS are forwarded to the leader, so they can be run on any coordinator. - SHOW REPLICATION LAG reports why the lag is unavailable via ReplicationLagUnavailable. - ADD COORDINATOR, REMOVE COORDINATOR and UPDATE CONFIG fail with an explicit "coordinator is not a leader" error. Adds a new "When there is no leader to serve the query" error handling section, the ShowCoordSettingsRpc / YieldLeadershipRpc entries, and v3.13.0 release notes. Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- .../ha-commands-reference.mdx | 145 +++++++++++++++--- .../how-high-availability-works.mdx | 54 +++++-- ...rying-the-cluster-in-high-availability.mdx | 2 + .../setup-ha-cluster-docker.mdx | 3 +- pages/release-notes.mdx | 20 +++ 5 files changed, 186 insertions(+), 38 deletions(-) diff --git a/pages/clustering/high-availability/ha-commands-reference.mdx b/pages/clustering/high-availability/ha-commands-reference.mdx index 572c1af60..8abb5f194 100644 --- a/pages/clustering/high-availability/ha-commands-reference.mdx +++ b/pages/clustering/high-availability/ha-commands-reference.mdx @@ -20,12 +20,21 @@ setup, the choice no longer matters.
-All queries can be run on any coordinator. If currently the coordinator is not a -leader, the query will be automatically forwarded to the current leader and -executed there. This is because the Raft protocol specifies that only the -leader should accept changes in the cluster. The only exception is [`YIELD -LEADERSHIP`](#yield-leadership), which must be run directly on the current -leader. +All queries can be run on any coordinator. If the coordinator you are connected +to is not the leader, the query is automatically forwarded to the current leader +and executed there. This is because the Raft protocol specifies that only the +leader should accept changes in the cluster, and because only the leader holds an +up-to-date view of the cluster. + +This also holds for the read-only cluster queries β€” [`SHOW +INSTANCES`](#show-instances), [`SHOW COORDINATOR +SETTINGS`](#coordinator-runtime-settings) and [`SHOW REPLICATION +LAG`](#show-replication-lag) β€” which are always answered by the leader, and for +[`YIELD LEADERSHIP`](#yield-leadership). Followers never answer them from their +own local state, so you never see a stale or partial picture of the cluster. If +the leader cannot be reached, these queries return **no rows** together with a +warning notification instead of a degraded result β€” see [Error +handling](#error-handling). ### `ADD COORDINATOR` @@ -88,8 +97,8 @@ REMOVE COORDINATOR coordinatorId; - Leader coordinator **cannot** remove itself. To remove the leader, first trigger a leadership change with [`YIELD - LEADERSHIP`](#yield-leadership) and then run `REMOVE COORDINATOR` against the - new leader. + LEADERSHIP`](#yield-leadership) and then run `REMOVE COORDINATOR` once a new + leader has been elected. {

Example

} @@ -313,16 +322,17 @@ informational notification that the request was submitted. {

Behavior

} -- Must be run **on the coordinator that is currently the leader**. Unlike most - other cluster management queries, this query is **not forwarded** to the - leader β€” running it on a follower fails with: - - > Only the current leader can yield the leadership! - +- Can be run on **any coordinator**. If the coordinator is a follower, the query + is forwarded to the current leader, which yields its leadership. You no longer + have to find the leader first. - Running it on a data instance fails with: > Only coordinator can run YIELD LEADERSHIP query. +- A coordinator that Raft already elected as leader but that has not yet + finished taking over the cluster still yields its leadership. This makes the + query usable as an escape hatch exactly when it is needed most β€” when a + freshly elected leader is stuck and you want another coordinator to take over. - The request is handed over to Raft and processed **asynchronously**. The query returns as soon as the request is submitted, not when the new leader is elected. @@ -334,15 +344,24 @@ informational notification that the request was submitted. checks toward all data instances. If no MAIN is found at that point, it performs a failover. +{

Failure modes

} + +| Error message | Meaning | +| ------------- | ------- | +| `Yielding leadership failed since the instance is not leader anymore!` | The request reached a coordinator that is no longer the leader β€” leadership changed in the meantime. Retry. | +| `Tried to forward the request to the current leader but the leader couldn't be found!` | There is currently no known leader to forward the request to (for example, an election is in progress). Retry once a leader is elected. | +| `Request forwarded to the leader but leader failed with request processing! Check logs on the leader to find out what happened!` | The leader was reached but failed to process the request. Inspect the leader's logs. | + {

Implications

} - This changes only the **coordinator** leadership. Data instances keep their MAIN and REPLICA roles β€” this is not a data failover, and client queries against MAIN and REPLICAs are unaffected. - During the short election window, cluster management queries (e.g. `SHOW - INSTANCES`, registration queries) may temporarily fail or report instances as - `down` because there is no leader to serve them. Retry once the new leader is - elected. + INSTANCES`, registration queries) may temporarily fail because there is no + leader to serve them. Read queries such as `SHOW INSTANCES` return no rows and + a warning notification rather than a stale picture of the cluster. Retry once + the new leader is elected. - At least one other healthy coordinator must be able to take over. In a single-coordinator cluster, or when the other coordinators are down, the same coordinator remains (or becomes again) the leader. @@ -386,14 +405,42 @@ SHOW INSTANCES; {

Output includes

} 1. Network endpoints (bolt, coordinator, management) -2. Health state -3. Role: MAIN, REPLICA, LEADER, FOLLOWER, or UNKNOWN +2. Health state (`up` or `down`) +3. Role: MAIN, REPLICA, LEADER or FOLLOWER 4. Time since last health ping -{

Behavior on followers

} +A data instance that is currently `down` keeps the role recorded in the Raft log +(`main` or `replica`) instead of being reported with an unknown role, so you can +still tell which instance the cluster considers MAIN while it is unreachable. + +{

Behavior

} + +The query is **strongly consistent**: the result always comes from the leader +coordinator, which is the only coordinator with an up-to-date view of the +cluster. + +1. If you are connected to the leader, it answers directly. +2. If you are connected to a follower, the follower forwards the request to the + leader and returns the leader's result. +3. If the leader cannot be reached, the query returns an **empty result set** + together with a `LeaderNotReachable` warning notification: -1. Follower attempts to query the leader for accurate state. -2. If leader unavailable, follower reports all servers as `"down"`. + > Couldn't reach the leader coordinator, so the state of the cluster is + > unknown. Please retry the query. + + This happens when no leader is currently elected, when the connection to the + leader is broken, or when the coordinator you are connected to was just + elected leader but has not finished taking over the cluster yet. + + +**Behavior change in Memgraph 3.13:** previously, a follower that could not +reach the leader fell back to reporting the cluster from its own local Raft +state, with health reported as `unknown`. It now returns no rows and a warning +notification instead, so a partial or stale cluster picture can never be +mistaken for the real one. If you have tooling or health checks that parse +`SHOW INSTANCES`, treat an empty result as "cluster state unknown, retry" rather +than as "no instances registered". + ### `SHOW INSTANCE` @@ -423,6 +470,23 @@ Shows replication lag (in committed transactions) for all instances. SHOW REPLICATION LAG; ``` +{

Behavior

} + +The lag data is collected by the leader coordinator from the current MAIN, so the +query is answered by the leader β€” a follower forwards the request and returns the +leader's result. + +Whenever the lag cannot be determined, the query returns **no rows** and a +warning notification explaining why, so you know whether retrying will help: + +| Notification code | Message | Meaning | +| ----------------- | ------- | ------- | +| `LeaderNotReachable` | Couldn't reach the leader coordinator, so the replication lag is unknown. Please retry the query. | No leader could be contacted (for example, an election is in progress). | +| `ReplicationLagUnavailable` | The leader coordinator hasn't finished taking over the cluster, so the replication lag is unknown. Please retry the query. | A new leader was elected but has not finished reconciling the cluster. | +| `ReplicationLagUnavailable` | No instance is currently main, so there is no replication lag to report. | The cluster has no MAIN β€” promote one with `SET INSTANCE ... TO MAIN`, or wait for failover. | +| `ReplicationLagUnavailable` | The current main didn't respond, so the replication lag is unknown. Check whether the main is up. | The MAIN did not answer the leader's request. | +| `ReplicationLagUnavailable` | The instance the leader considers main reports that it is a replica, so the replication lag is unknown. Please retry the query once the cluster state is reconciled. | The leader's view is stale; the [reconciliation loop](/clustering/high-availability/how-high-availability-works#how-the-reconciliation-loop-works) will fix it. | + {

Implications

} - Lag values survive restarts (stored in snapshots + WAL). @@ -490,6 +554,14 @@ cluster without downtime. Use `SET COORDINATOR SETTING` to modify a value and `SHOW COORDINATOR SETTINGS` to inspect all current values. Changes propagate automatically to every coordinator in the cluster. +Both queries can be run on any coordinator and are served by the leader β€” a +follower forwards the request and returns the leader's answer. If the leader +cannot be reached, `SHOW COORDINATOR SETTINGS` returns **no rows** together with a +`LeaderNotReachable` warning notification: + +> Couldn't reach the leader coordinator, so the coordinator settings are unknown. +> Please retry the query. + ### `instance_health_check_frequency_sec` How often the coordinator pings data instances, in seconds. @@ -653,6 +725,35 @@ promote, demote, add coordinator), the error message will indicate: > Writing to Raft log failed. Please retry the operation. +### When there is no leader to serve the query + +Because every cluster query is served by the leader, queries fail (or return +nothing) while the cluster has no usable leader β€” most commonly during a leader +election, or right after one, while the new leader is still taking over the +cluster. + +State-changing queries (`ADD COORDINATOR`, `REMOVE COORDINATOR`, `UPDATE +CONFIG`, `REGISTER INSTANCE`, `UNREGISTER INSTANCE`, `SET INSTANCE ... TO MAIN`, +`DEMOTE INSTANCE`, `FORCE RESET CLUSTER STATE`) fail with an explicit error: + +> Couldn't <operation> since coordinator is not a leader! Try contacting +> other coordinators as there might be leader election happening or other +> coordinators are down. + +When the leader is known but the request still could not be executed there, the +message instead names the current leader's id and Bolt address so you can +connect to it directly. If the request could not be forwarded at all, the error +is: + +> Tried to forward the request to the current leader but the leader couldn't be +> found! + +Read queries (`SHOW INSTANCES`, `SHOW COORDINATOR SETTINGS`, `SHOW REPLICATION +LAG`) do not fail β€” they return an **empty result set** with a warning +notification (`LeaderNotReachable`, or `ReplicationLagUnavailable` for `SHOW +REPLICATION LAG`). In both cases the operation is safe to retry once a leader is +available. + ## Troubleshooting commands diff --git a/pages/clustering/high-availability/how-high-availability-works.mdx b/pages/clustering/high-availability/how-high-availability-works.mdx index 55845735e..dcdcc4ce2 100644 --- a/pages/clustering/high-availability/how-high-availability-works.mdx +++ b/pages/clustering/high-availability/how-high-availability-works.mdx @@ -161,6 +161,8 @@ Below is a cleaned-up categorization. | RPC | Purpose | Description | | ------------------------ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `ShowInstancesRpc` | Follower requests cluster state from leader. | Sent by a follower coordinator to the leader coordinator when a user executes `SHOW INSTANCES` through the follower. | +| `ShowCoordSettingsRpc` | Follower requests coordinator settings. | Sent by a follower coordinator to the leader coordinator when a user executes `SHOW COORDINATOR SETTINGS` through the follower. | +| `YieldLeadershipRpc` | Follower asks the leader to step down. | Sent by a follower coordinator to the leader coordinator when a user executes `YIELD LEADERSHIP` through the follower. | | `AddCoordinatorRpc` | Follower requests adding coordinator. | Sent by a follower coordinator to the leader coordinator when a user executes `ADD COORDINATOR` through the follower. | | `RemoveCoordinatorRpc` | Follower requests removing coordinator. | Sent by a follower coordinator to the leader coordinator when a user executes `REMOVE COORDINATOR` through the follower. | | `RegisterInstanceRpc` | Follower requests registering an instance. | Sent by a follower coordinator to the leader coordinator when a user executes `REGISTER INSTANCE` through the follower. | @@ -546,21 +548,43 @@ modes](/clustering/replication/how-replication-works#replication-modes). ## Actions on follower coordinators -Follower coordinators operate in a restricted mode. -They can **only execute** the `SHOW INSTANCES` command. - -All state-changing operations are disabled on followers, including: - -- Registering data instances -- Unregistering data instances -- Demoting an instance -- Promoting an instance to MAIN -- Forcing a cluster state reset -- Yielding leadership - -These operations are permitted **only on the leader coordinator**. Note that -[`YIELD LEADERSHIP`](/clustering/high-availability/ha-commands-reference#yield-leadership) -is not forwarded to the leader β€” it fails on a follower instead. +Follower coordinators never execute cluster operations themselves. Instead, they +act as a transparent entry point: every cluster query you run on a follower is +**forwarded to the current leader**, executed there, and the leader's answer is +returned to you. This holds both for state-changing operations (registering and +unregistering data instances, promoting and demoting instances, adding and +removing coordinators, updating configuration, forcing a cluster state reset, +[yielding +leadership](/clustering/high-availability/ha-commands-reference#yield-leadership)) +and for read-only ones (`SHOW INSTANCES`, `SHOW COORDINATOR SETTINGS`, `SHOW +REPLICATION LAG`, and `bolt+routing` routing table requests). + +As a result you can point your tooling at any coordinator without first having to +discover which one is the leader. + +### Why followers never answer from local state + +A follower's own Raft state is not enough to describe the cluster: health of the +data instances is only known to the leader, which is the coordinator that pings +them. For this reason followers do not fall back to a local, partial answer when +the leader cannot be reached. Instead: + +- Read queries return an **empty result set** together with a warning + notification (`LeaderNotReachable`, or `ReplicationLagUnavailable` for `SHOW + REPLICATION LAG`). +- State-changing queries fail with an error telling you that the coordinator is + not the leader, or that the leader could not be found. + +Both cases mean "cluster state unknown β€” retry", and both are expected during the +brief window of a leader election or while a newly elected leader is still taking +over the cluster. See [Error +handling](/clustering/high-availability/ha-commands-reference#when-there-is-no-leader-to-serve-the-query) +for the exact messages. + +The one exception is the `bolt+routing` routing table: a coordinator that Raft +elected as leader answers routing requests from its own Raft state even before it +has finished taking over the cluster, so that clients can keep routing queries +during the leadership transition. ## Raft-first operations and the reconciliation loop diff --git a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx index ebb6e6789..b9275c9b2 100644 --- a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx +++ b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx @@ -64,6 +64,8 @@ The routing protocol works as follows: receives the latest routing table. - If it contacts a **follower**, the follower forwards the request to the leader and returns the leader’s result. +- If the leader cannot be reached at all, an **empty routing table** is returned + and the driver retries against another coordinator. Because leader state is synchronized via Raft, routing information is always accurate. diff --git a/pages/clustering/high-availability/setup-ha-cluster-docker.mdx b/pages/clustering/high-availability/setup-ha-cluster-docker.mdx index 0394b8239..5c0c83e2e 100644 --- a/pages/clustering/high-availability/setup-ha-cluster-docker.mdx +++ b/pages/clustering/high-availability/setup-ha-cluster-docker.mdx @@ -202,7 +202,8 @@ SET INSTANCE instance_3 TO MAIN; ## Check cluster state -Connect to the leader coordinator and check cluster state with `SHOW INSTANCES`; +Connect to any coordinator and check cluster state with `SHOW INSTANCES`. The +query is always answered by the leader, so followers report the same state: | name | bolt_server | coordinator_server | management_server | health | role | last_succ_resp_ms | | ------------- | -------------- | ------------------ | ----------------- | ------ | -------- | ---------------- | diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index cbc629c32..7592c5798 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -58,6 +58,15 @@ guide. you need the previous Python behavior, or update consumers for the new structured / ISO-8601 JSON forms. [#4443](https://github.com/memgraph/memgraph/pull/4443) +- `SHOW INSTANCES` is now strongly consistent and always answered by the leader + coordinator. A follower that cannot reach the leader no longer falls back to + reporting the cluster from its own local Raft state with `unknown` health β€” + it returns an empty result set with a `LeaderNotReachable` warning + notification instead. Tooling that parses `SHOW INSTANCES` should treat an + empty result as "cluster state unknown, retry" rather than "no instances + registered". A data instance that is down now reports the role recorded in the + Raft log (`main` / `replica`) instead of an unknown role. + [#4492](https://github.com/memgraph/memgraph/pull/4492) - `TERMINATE TRANSACTIONS` now requires transaction ids to parse in full. Ids with trailing characters previously terminated the transaction matching the numeric prefix, and unparseable ids were reported back as an attempt on @@ -137,6 +146,17 @@ guide. statements). Under index-heavy workloads this removes GC-correlated latency spikes on index and constraint creation. [#4468](https://github.com/memgraph/memgraph/pull/4468) +- `YIELD LEADERSHIP` and `SHOW COORDINATOR SETTINGS` can now be run on any + coordinator β€” followers forward them to the leader instead of failing or + answering from local state. When no leader can serve a cluster query, + Memgraph now says so explicitly: read queries (`SHOW INSTANCES`, `SHOW + COORDINATOR SETTINGS`, `SHOW REPLICATION LAG`) return no rows with a + `LeaderNotReachable` warning, `SHOW REPLICATION LAG` additionally reports why + the lag is unavailable via `ReplicationLagUnavailable` (no current main, main + unresponsive, leader still taking over, stale leader view), and + `ADD COORDINATOR`, `REMOVE COORDINATOR` and `UPDATE CONFIG` fail with a clear + "coordinator is not a leader" error instead of a misleading one. + [#4492](https://github.com/memgraph/memgraph/pull/4492) {

✨ New features

} From 5ab450b2bc1f485485c8efb9cc642fa6dec7288f Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 14:06:22 +0200 Subject: [PATCH 18/19] feat: SSO coords (#1708) * feat: SSO coords * feat: Remove --init-file comment * fix: Notes if SSO on data instances is already set-up * fix: State definitively that --init-file is unsupported in HA mode * docs: SHOW VERSION query (#1715) --------- Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- pages/clustering/high-availability.mdx | 5 + pages/clustering/high-availability/_meta.ts | 1 + .../coordinator-authentication.mdx | 592 ++++++++++++++++++ .../ha-commands-reference.mdx | 183 +++++- .../how-high-availability-works.mdx | 111 +++- ...rying-the-cluster-in-high-availability.mdx | 42 +- .../auth-system-integrations.mdx | 23 + .../query-privileges.mdx | 32 +- .../role-based-access-control.mdx | 17 +- .../enabling-memgraph-enterprise.mdx | 6 + pages/database-management/server-stats.mdx | 7 + pages/release-notes.mdx | 44 ++ 12 files changed, 1038 insertions(+), 25 deletions(-) create mode 100644 pages/clustering/high-availability/coordinator-authentication.mdx diff --git a/pages/clustering/high-availability.mdx b/pages/clustering/high-availability.mdx index d7f8dbcca..f999f27a9 100644 --- a/pages/clustering/high-availability.mdx +++ b/pages/clustering/high-availability.mdx @@ -49,6 +49,11 @@ recommended configuration patterns. Recommended practices for running a robust, reliable, and well-observed HA deployment. +### [Coordinator authentication](/clustering/high-availability/coordinator-authentication) + +Secure the cluster's control plane with single sign-on, Raft-replicated +coordinator roles, and the `COORDINATOR_READ` / `COORDINATOR_WRITE` privileges. + ### [Bulk import in analytical mode](/clustering/high-availability/analytical-import) Import data into a cluster using the in-memory analytical storage mode, then diff --git a/pages/clustering/high-availability/_meta.ts b/pages/clustering/high-availability/_meta.ts index 8a617d6ce..671508ae1 100644 --- a/pages/clustering/high-availability/_meta.ts +++ b/pages/clustering/high-availability/_meta.ts @@ -1,6 +1,7 @@ export default { "how-high-availability-works": "Under the hood", "querying-the-cluster-in-high-availability": "Querying the cluster in HA", + "coordinator-authentication": "Coordinator authentication", "setup-ha-cluster-docker": "Set up HA cluster with Docker", "setup-ha-cluster-docker-compose": "Set up HA cluster with Docker Compose", "setup-ha-cluster-k8s": "Set up HA cluster with K8s", diff --git a/pages/clustering/high-availability/coordinator-authentication.mdx b/pages/clustering/high-availability/coordinator-authentication.mdx new file mode 100644 index 000000000..6ec9b1223 --- /dev/null +++ b/pages/clustering/high-availability/coordinator-authentication.mdx @@ -0,0 +1,592 @@ +--- +title: Coordinator authentication and authorization +description: Learn how to secure Memgraph high availability coordinators with single sign-on, Raft-replicated roles and the COORDINATOR_READ and COORDINATOR_WRITE privileges. +--- + +import { Callout } from 'nextra/components' +import { Steps } from 'nextra/components' +import {CommunityLinks} from '/components/social-card/CommunityLinks' + +# Coordinator authentication and authorization Enterprise + +Coordinators are the control plane of a [high availability +cluster](/clustering/high-availability): they register and unregister data +instances, promote and demote MAIN, hand out the routing table, and change +cluster-wide settings. From Memgraph 3.13, that control plane can be protected +with [single sign-on (SSO)](/database-management/authentication-and-authorization/auth-system-integrations#single-sign-on) +against your corporate identity provider (IdP), backed by a small set of +Raft-replicated roles. + + +SSO authentication on coordinators, coordinator role management, privilege +grants and privilege enforcement are **Memgraph Enterprise** features and +require a valid license. Without a valid license, coordinators keep the +pre-3.13 behavior: username/password connections are accepted as a passthrough +with full access. + + +Before continuing, read [how high availability +works](/clustering/high-availability/how-high-availability-works) and [querying +the cluster in high +availability](/clustering/high-availability/querying-the-cluster-in-high-availability). + +## How coordinator auth differs from data instances + +Coordinators do **not** store users. There is no auth key-value store on a +coordinator, no `CREATE USER`, no passwords, and no fine-grained access control. +The only authorization state a coordinator keeps is a list of **roles**, each +carrying a coordinator privilege mask. + +| | Data instance | Coordinator | +|---|---|---| +| Users | Stored in the auth store | Not supported | +| Roles | Stored in the auth store | Stored in the **Raft-replicated cluster state** | +| Privileges | Full privilege set + fine-grained access control | Exactly two: `COORDINATOR_READ`, `COORDINATOR_WRITE` | +| Basic auth | Username and password are validated | Passthrough β€” credentials are ignored (see [below](#basic-authentication-passthrough)) | +| SSO | Supported (OIDC, SAML, Kerberos) | Supported (OIDC, SAML, Kerberos) | + +Because roles live in the Raft log, they survive coordinator restarts, are +replicated to every coordinator, are reconstructed by a follower that rejoins +after being down, and survive leader failover. + + +**Breaking change in Memgraph 3.13:** the `COORDINATOR` privilege has been +removed. It never gated any operation. It is no longer accepted in `GRANT` / +`DENY` / `REVOKE` statements and is no longer reported by `SHOW PRIVILEGES`. A +stale `COORDINATOR` grant in an existing deployment is simply never reported and +never checked β€” no migration is required. Coordinator access is now controlled +by the new `COORDINATOR_READ` and `COORDINATOR_WRITE` privileges described +below, which are granted **on the coordinators themselves**, not on data +instances. + + +## Coordinator privileges + +Coordinators enforce exactly two privileges: + +| Privilege | Grants | +|---|---| +| `COORDINATOR_READ` | Reading the routing table and all read-only introspection queries. | +| `COORDINATOR_WRITE` | Every query runnable on a coordinator. `COORDINATOR_WRITE` is a **superset** of `COORDINATOR_READ`. | + +These names were chosen because bare `READ` and `WRITE` already exist as +data-instance fine-grained privileges. They are meaningful **only on +coordinators** and are deliberately excluded from the data-instance privilege +set, so `GRANT ALL PRIVILEGES` on a data instance does not grant them. + +A session's effective privilege is the **union** of the masks of all its roles. +A role with no grant confers nothing. + +### Which privilege each query requires + +Read-only introspection requires `COORDINATOR_READ`; every mutating or +administrative query requires `COORDINATOR_WRITE`. A `COORDINATOR_WRITE` grant +satisfies a `COORDINATOR_READ` requirement, but not the other way round. + +| Query | Required privilege | +|---|---| +| Routing table (Bolt `ROUTE` message) | `COORDINATOR_READ` | +| [`SHOW INSTANCE`](/clustering/high-availability/ha-commands-reference#show-instance) | `COORDINATOR_READ` | +| [`SHOW INSTANCES`](/clustering/high-availability/ha-commands-reference#show-instances) | `COORDINATOR_READ` | +| [`SHOW COORDINATOR SETTINGS`](/clustering/high-availability/ha-commands-reference#coordinator-runtime-settings) | `COORDINATOR_READ` | +| [`SHOW REPLICATION LAG`](/clustering/high-availability/ha-commands-reference#show-replication-lag) | `COORDINATOR_READ` | +| `SHOW ROLES` | `COORDINATOR_READ` | +| `SHOW PRIVILEGES FOR ROLE ` | `COORDINATOR_READ` | +| `SHOW CONFIG`, `SHOW SETTING`, system info queries | `COORDINATOR_READ` | +| [`SHOW VERSION`](/database-management/server-stats#instance-version) | `COORDINATOR_READ` | +| `SHOW CURRENT USER`, `SHOW CURRENT ROLE` | **None** (self-service, see [below](#self-service-identity-queries)) | +| [`REGISTER INSTANCE`](/clustering/high-availability/ha-commands-reference#register-instance) / [`UNREGISTER INSTANCE`](/clustering/high-availability/ha-commands-reference#unregister-instance) | `COORDINATOR_WRITE` | +| [`SET INSTANCE ... TO MAIN`](/clustering/high-availability/ha-commands-reference#set-instance--to-main) / [`DEMOTE INSTANCE`](/clustering/high-availability/ha-commands-reference#demote-instance) | `COORDINATOR_WRITE` | +| [`ADD COORDINATOR`](/clustering/high-availability/ha-commands-reference#add-coordinator) / [`REMOVE COORDINATOR`](/clustering/high-availability/ha-commands-reference#remove-coordinator) | `COORDINATOR_WRITE` | +| [`UPDATE CONFIG`](/clustering/high-availability/ha-commands-reference#update-config) | `COORDINATOR_WRITE` | +| [`YIELD LEADERSHIP`](/clustering/high-availability/ha-commands-reference#yield-leadership) | `COORDINATOR_WRITE` | +| [`SET COORDINATOR SETTING`](/clustering/high-availability/ha-commands-reference#coordinator-runtime-settings) | `COORDINATOR_WRITE` | +| [`FORCE RESET CLUSTER STATE`](/clustering/high-availability/ha-commands-reference#force-reset-cluster-state) | `COORDINATOR_WRITE` | +| `SET SETTING` | `COORDINATOR_WRITE` | +| [`RELOAD BOLT_SERVER TLS` / `RELOAD INTRA_CLUSTER TLS`](/database-management/ssl-encryption#reload-ssl-certificates-at-runtime) | `COORDINATOR_WRITE` | +| `CREATE ROLE`, `DROP ROLE`, `GRANT`, `REVOKE` | `COORDINATOR_WRITE` | + +Anything not recognized fails closed and requires `COORDINATOR_WRITE`. + +## Authentication modes + +A coordinator accepts exactly two kinds of Bolt connection: **basic/none** and +an **SSO scheme listed in `--auth-module-mappings`**. Any other scheme is +rejected with: + +``` +The "" authentication scheme isn't supported on this coordinator; +connect with basic auth or an SSO scheme listed in the auth-module-mappings flag. +``` + +### Basic authentication passthrough + +When SSO is not in effect, connecting with a username and password (or with no +auth at all) **succeeds and the credentials are ignored**. The session gets full +`COORDINATOR_WRITE` access. This is the pre-3.13 behavior, it requires no +license, and it keeps existing admin tooling working unchanged. + +Basic/none authentication is **denied** only when **all three** of the following +hold: + +1. SSO is configured β€” `--auth-module-mappings` is non-empty. +2. The enterprise license is valid. +3. The committed role set contains at least one role holding + `COORDINATOR_WRITE`. + +In that case the connection is rejected with: + +``` +Basic authentication is disabled on this coordinator because SSO is configured; +connect with an SSO scheme listed in the auth-module-mappings flag. +``` + + +**Anybody can log in until a `COORDINATOR_WRITE` role exists.** Condition 3 is +what makes coordinator SSO self-bootstrapping. On a coordinator that starts with +`--auth-module-mappings` set but an empty role set, SSO cannot yet grant a +privileged session to anybody β€” so basic auth stays open, and you use it to +create the first role and grant it `COORDINATOR_WRITE`. The moment that grant +commits to Raft, basic auth closes on the next login attempt and SSO takes over. +**No coordinator restart is required.** + +The same rule applies in reverse: if you drop or revoke the last +`COORDINATOR_WRITE` role on a live cluster, basic auth reopens rather than +leaving the cluster unadministrable. + + + +**Break-glass on license loss.** If the enterprise license is missing, expired +or invalid, SSO rejects every login. Condition 2 above means basic auth falls +back to the passthrough in exactly that case, so a license transition can never +lock every Bolt session out of a coordinator. Use that session to re-install the +license over Bolt: + +```cypher +SET DATABASE SETTING 'enterprise.license' TO 'License'; +SET DATABASE SETTING 'organization.name' TO 'Organization'; +``` + +The license check for this decision is the full, non-cached check β€” a license +that expires by date takes effect immediately rather than at the next cache +refresh. + + +### A follower without quorum cannot be logged into + +Both the basic-auth decision and the SSO role check need the **leader's** +committed role set, and both are **fail-closed**: when the leader cannot be +reached, the login is rejected rather than validated against possibly-stale +local replicated state, which could still list a dropped role or an +already-revoked privilege mask. + +So on a coordinator that has lost quorum (it is a follower, no leader is +elected, or the leader is unreachable) and has SSO configured with a valid +license: + +- **SSO logins are rejected** with: + + ``` + SSO authentication failed: the coordinator leader is unreachable, so roles + can't be validated. Retry once a leader is elected. + ``` + +- **Basic/none logins are also rejected.** An unknown role set is not treated as + "no writable role", so the break-glass path does not open during a transient + leader outage. + +This is intentional and temporary: SSO is unavailable in that window anyway, and +access returns as soon as a leader is elected. If SSO is **not** configured, the +basic-auth passthrough never contacts the leader, so it keeps working on a +partitioned follower. + +### SSO authentication + +For a scheme present in `--auth-module-mappings`, the coordinator runs the +corresponding auth module and genuinely authenticates the connection. The same +[built-in and custom auth +modules](/database-management/authentication-and-authorization/auth-system-integrations) +used on data instances work here, and `MEMGRAPH_SSO_*` environment variables are +inherited by the module subprocess exactly as they are on data instances β€” there +is no separate coordinator-side SSO configuration. + +The login is accepted only when **all** of the following hold: + +1. The module authenticates the identity (valid, unexpired IdP token). +2. The module returns at least one role. +3. **Every** role the module returns exists in the coordinator's committed role + set. A multi-role response succeeds only when all of its roles exist. +4. The union of those roles' masks grants at least `COORDINATOR_READ`. + + +**A role without privileges cannot be used to log in.** If the roles all exist +but none of them has been granted `COORDINATOR_READ` or `COORDINATOR_WRITE`, the +connection is **rejected at login** rather than admitted as a session that would +be denied every query β€” including the routing table. Grant a privilege to the +role before mapping identities onto it. + + +Each rejection reason has its own message, so an operator rolling SSO out can +tell a bad token apart from a misconfigured role mapping: + +| Situation | Error returned to the client | +|---|---| +| Invalid/expired token, module failure, or missing license | `SSO authentication failed: the identity provider token was rejected, the auth module failed, or the enterprise license is missing.` | +| Module returned no roles | `SSO authentication failed: the identity provider returned no roles for this identity. Map the identity's group to a coordinator role.` | +| A returned role does not exist on the coordinator | `SSO authentication failed: the identity provider returned a role that doesn't exist on this coordinator. Create it with CREATE ROLE, or fix the identity provider mapping.` | +| Roles exist but carry no privilege | `SSO authentication failed: this identity's role(s) exist but carry no coordinator privilege. Grant COORDINATOR_READ or COORDINATOR_WRITE to one of them.` | +| Leader unreachable | `SSO authentication failed: the coordinator leader is unreachable, so roles can't be validated. Retry once a leader is elected.` | + +The offending role name is written to the coordinator log, **not** returned to +the client, so a rejected login cannot be used to enumerate the coordinator's +role set. + +The username the module reports is recorded as the session principal and is used +for [audit logging](/database-management/logs) and `SHOW CURRENT USER`. A module +that omits the username still logs in β€” the coordinator authorizes by role β€” but +its queries cannot be attributed to a person, and a warning is logged once at +login. + +## Managing coordinator roles and privileges + +All role and privilege queries are documented in the [HA reference +commands](/clustering/high-availability/ha-commands-reference#coordinator-role-and-privilege-management) +guide. In short: + +```cypher +CREATE ROLE ops; +CREATE ROLE IF NOT EXISTS ops; +DROP ROLE ops; +SHOW ROLES; + +GRANT COORDINATOR_READ TO ops; +GRANT COORDINATOR_WRITE TO ops; +GRANT ALL PRIVILEGES TO ops; -- grants both coordinator privileges +REVOKE COORDINATOR_WRITE FROM ops; +REVOKE ALL PRIVILEGES FROM ops; -- removes both + +SHOW PRIVILEGES FOR ROLE ops; +``` + +All of these can be run on **any** coordinator: on a follower they are +transparently forwarded to the leader. Writes are committed through the Raft +log; `SHOW ROLES` and `SHOW PRIVILEGES FOR ROLE` are strong reads served by the +leader. + +### Self-service identity queries + +`SHOW CURRENT USER` and `SHOW CURRENT ROLE` are exempt from the privilege check +and from the license gate β€” they only reveal the session's own identity, so even +a session whose roles were revoked mid-flight can still inspect who it is. + +- `SHOW CURRENT USER` returns the principal the identity provider authenticated. + It is purely session-local and works even when the leader is unreachable. A + basic-auth passthrough session authenticated no principal and returns `null`. +- `SHOW CURRENT ROLE` returns the session's roles **filtered against the + leader's committed role set**, so it stops naming a role that `DROP ROLE` + already removed. A basic-auth passthrough session has no roles and returns + `null`. + +### Auth queries rejected on coordinators + +Everything outside the small surface above is rejected with: + +``` +Coordinator can run only coordinator queries! +``` + +That includes: + +- User management: `CREATE USER`, `SET PASSWORD`, `SHOW USERS`, `SET ROLE`, + `GRANT ROLE`. +- `DENY` in **any** form. +- `GRANT` / `REVOKE` targeting a `USER` (`GRANT COORDINATOR_READ TO USER foo`). +- Privilege lists containing any non-coordinator privilege + (`GRANT MATCH TO ops`). +- [Fine-grained access + control](/database-management/authentication-and-authorization/role-based-access-control#fine-grained-access-control) + β€” `GRANT ... ON NODES ...` / `ON EDGES ...` β€” coordinators have no graph. +- Property permissions. +- [Multi-tenancy](/database-management/multi-tenancy) database access: + `GRANT DATABASE`, `REVOKE DATABASE`, `SET MAIN DATABASE`. +- `SHOW PRIVILEGES FOR USER `. +- `SHOW PRIVILEGES FOR ROLE ` with a trailing `ON MAIN`, `ON CURRENT` or + `ON DATABASE ` clause β€” coordinators have no databases. + +## Privileges are re-checked on every query + +An SSO session does **not** cache the privilege mask it was given at login. On +**every** query β€” and on every routing-table request β€” the coordinator +re-derives the session's effective mask from the **leader's** committed role set: +if this coordinator is the ready leader it reads locally, otherwise it sends a +`GetRolesRpc` to the leader. + +This has three consequences worth planning for: + +- **`REVOKE` and `DROP ROLE` take effect immediately**, without the client + reconnecting. Long-lived connections β€” driver routing pools, open admin shells + β€” are downgraded on their very next query. A session whose roles were all + dropped keeps its connection but is denied every privileged query. +- **A leader outage denies queries on SSO sessions.** With no readable role set + the effective mask is empty (fail-closed), so queries fail until a leader is + elected. Retry once `SHOW INSTANCES` reports a leader again. +- **Basic-auth passthrough sessions are unaffected.** They carry no roles, so + they keep their login-time full `COORDINATOR_WRITE` mask and never contact the + leader for a privilege check. + +Denied queries fail with: + +``` +You don't have the required privilege to run this query on the coordinator! +``` + +and a denied routing-table request fails with: + +``` +You don't have permission to read the routing table on the coordinator! +``` + +The routing-table denial is reported as a non-retryable client error, so drivers +do not retry it as if it were a transient failure. + +## Bolt+routing with SSO + + +**A `neo4j://` routing connection works only if the same roles exist on both the +coordinators and the data instances.** + + +[Bolt+routing](/clustering/high-availability/querying-the-cluster-in-high-availability) +is entirely client-side, and the driver reuses **one set of credentials for both +legs** of the connection. With SSO in the picture, a single `neo4j://` session +therefore performs two independent authentications: + + + +{

The driver authenticates against a coordinator

} + +It sends the SSO scheme and IdP token to a coordinator and issues a `ROUTE` +message. The coordinator runs the auth module, requires every returned role to +exist **in the Raft-replicated coordinator role set**, and requires the union of +their masks to grant at least `COORDINATOR_READ` β€” otherwise the routing table +request is denied. + +{

The driver authenticates against a data instance

} + +Using the routing table, the driver opens a connection to MAIN or a REPLICA with +the **same scheme and the same token**. The data instance runs its own auth +module and requires every returned role to exist **in its auth store**, with +whatever data privileges (`MATCH`, `CREATE`, …) the query needs. + +
+ +For that to work end to end: + +| Requirement | Why | +|---|---| +| The **same SSO scheme** is listed in `--auth-module-mappings` on coordinators *and* on every data instance | The driver sends one scheme to both; a scheme absent from a node's mappings is rejected there. | +| The **same role names** the IdP returns exist on coordinators *and* on data instances | Each side validates the returned roles against its own role store, and any missing role rejects the whole login on that side. | +| Those roles hold `COORDINATOR_READ` (or `COORDINATOR_WRITE`) **on the coordinators** | Otherwise the `ROUTE` message is denied and the driver never obtains a routing table. | +| Those roles hold the needed **data privileges on the data instances** | The coordinator privileges are not visible to, and mean nothing on, a data instance. | + +The privilege *values* are necessarily different on the two sides β€” coordinators +know only `COORDINATOR_READ` / `COORDINATOR_WRITE`, data instances know only the +data privilege set β€” but the **role names must match**. A typical setup mirrors +each IdP group into a role of the same name on both sides: + +```cypher +-- On any coordinator +CREATE ROLE analyst; +GRANT COORDINATOR_READ TO analyst; + +CREATE ROLE dba; +GRANT ALL PRIVILEGES TO dba; -- COORDINATOR_READ + COORDINATOR_WRITE +``` + +```cypher +-- On the MAIN data instance (replicated to REPLICAs) +CREATE ROLE analyst; +GRANT MATCH TO analyst; + +CREATE ROLE dba; +GRANT ALL PRIVILEGES TO dba; -- the full data-instance privilege set +``` + + +A role that exists on the data instances but not on the coordinators produces a +confusing failure mode: direct `bolt://` connections to MAIN work, but every +`neo4j://` routing connection fails at the coordinator. Check `SHOW ROLES` on a +coordinator and `SHOW ROLES` on MAIN and reconcile the two lists. + + +## Rolling SSO out + +The bootstrap order differs between coordinators and data instances, and getting +it wrong is the most common way to lock yourself out. + +### On coordinators β€” no restart needed + +Coordinators can be started with `--auth-module-mappings` from the very +beginning. Because basic auth stays open until a `COORDINATOR_WRITE` role +exists, you can create the roles over Bolt on the running cluster. + + + +{

Start the coordinators with the SSO mapping

} + +``` +--auth-module-mappings=oidc-entra-id +``` + +Set the `MEMGRAPH_SSO_*` environment variables the module needs, exactly as you +would on a data instance. + +{

Connect with basic auth

} + +No `COORDINATOR_WRITE` role exists yet, so username/password (or no credentials +at all) is still accepted with full access: + +``` +mgconsole --host --port 7687 +``` + +{

Create the roles and grant privileges

} + +```cypher +CREATE ROLE dba; +GRANT ALL PRIVILEGES TO dba; + +CREATE ROLE analyst; +GRANT COORDINATOR_READ TO analyst; +``` + +The role names must match what your IdP returns β€” see [role +mapping](/database-management/authentication-and-authorization/auth-system-integrations#role-mapping). + +{

SSO is now enforced

} + +As soon as `GRANT ALL PRIVILEGES TO dba` commits to Raft, all three deny +conditions hold and the **next** basic-auth login attempt is rejected. Existing +basic-auth sessions keep their full mask until they disconnect. Verify with: + +```cypher +SHOW ROLES; +SHOW PRIVILEGES FOR ROLE dba; +``` + +
+ +### On data instances β€” restart required + + +This only concerns data instances where SSO is **not** set up yet. If your data +instances already authenticate through SSO, nothing changes for them β€” enabling +SSO on the coordinators does not require any modification on the data-instance +side, beyond using the [same role names](#boltrouting-with-sso) on both sides. + + +Data instances have no equivalent escape hatch. Their SSO path validates the +IdP's roles against the auth store, and a role that does not exist means the +login fails. If you enable the module before the roles exist, SSO users cannot +log in and β€” when the mapped scheme is `basic` (LDAP) β€” username/password +authentication is delegated to the module too, so you may have no way in at all +to create them. + +The safe order is therefore: + + + +{

Start the data instance *without* `--auth-module-mappings`

} + +{

Log in and create the roles

} + +```cypher +CREATE ROLE dba; +GRANT ALL PRIVILEGES TO dba; + +CREATE ROLE analyst; +GRANT MATCH TO analyst; +``` + +{

Restart the data instance with `--auth-module-mappings` set

} + +SSO logins now find their roles and succeed. + +
+ + +Do the coordinator side and the data-instance side with the **same role names**, +or [Bolt+routing](#boltrouting-with-sso) connections will fail even though +direct connections to each node work. + + +## Rolling upgrades + +The new role and privilege queries are carried by new coordinator-to-coordinator +RPCs. There is no RPC version negotiation. During a rolling upgrade, if a +new follower forwards a role or privilege query to a leader that has not been +upgraded yet, the query **fails with an error** rather than crashing the +coordinator: + +``` +Query forwarded to the leader but it failed to process the request! +Check the logs on the leader to find out what happened. +``` + +Complete the upgrade of all coordinators before managing roles, or run the role +queries directly against an upgraded leader. + +The Raft cluster state itself is version-safe in both directions: an older +coordinator ignores the unknown roles key, and a newer coordinator reading an +older log or snapshot sees an empty role set. No log store version bump is +involved. + +## Timeouts + +Role and privilege queries β€” like all forwarded coordinator queries β€” run on the +caller's Bolt session thread, so they carry explicit RPC timeouts to prevent a +session blocking forever against a reachable-but-stuck leader. See [RPC +timeouts](/clustering/high-availability/how-high-availability-works#rpc-timeouts) +for the full table. The values relevant to authentication are: + +| RPC | Timeout | Used by | +|---|---|---| +| `GetRolesReq` | 10s | SSO login role validation, the per-query privilege re-check, `SHOW ROLES`, `SHOW CURRENT ROLE` | +| `GetRolePrivilegesReq` | 10s | `SHOW PRIVILEGES FOR ROLE` | +| `CreateRoleReq` / `DropRoleReq` | 10s | `CREATE ROLE`, `DROP ROLE` | +| `GrantPrivilegeReq` / `RevokePrivilegeReq` | 10s | `GRANT`, `REVOKE` | + +Each of these budgets covers a Raft commit, which is itself capped at 3 seconds, +plus headroom. Hitting the timeout surfaces as a query error; retry the +operation. + +Independently of the RPC timeout, the auth module subprocess is bounded by +[`--auth-module-timeout-ms`](/database-management/authentication-and-authorization/auth-system-integrations#configuration-flags), +the same flag used on data instances. A module that exceeds it fails the login. + +## Limitations + +- **No users on coordinators.** Only the role set and each role's privilege mask + are persisted. There is no way to create a coordinator-local account. +- **No SSO auto-provisioning of roles.** Unlike some data-instance setups, an + SSO identity does not implicitly create `readonly` / `readwrite` / `admin` + roles on a coordinator. Roles must be created explicitly with `CREATE ROLE`. +- **Only two privileges.** The fine-grained data-instance privilege set is not + modeled on coordinators. +- **Role names** must match the + [`--auth-user-or-role-name-regex`](/database-management/configuration) pattern, + the same as on data instances. + +## Troubleshooting + +| Symptom | Likely cause | +|---|---| +| Basic auth suddenly rejected after a `GRANT` | Expected β€” a `COORDINATOR_WRITE` role now exists, so SSO is enforced. Connect with an SSO scheme. | +| Basic auth unexpectedly accepted while SSO is configured | Either the license is invalid, or no role holds `COORDINATOR_WRITE`. Check `SHOW PRIVILEGES FOR ROLE` and the license status. | +| Every login rejected on one coordinator | That coordinator cannot reach a leader. Check `SHOW INSTANCES` from another coordinator and wait for the election to finish. | +| SSO login rejected with "role that doesn't exist" | `CREATE ROLE` on the coordinator, or fix the IdP group mapping. Check `SHOW ROLES`. | +| SSO login rejected with "carry no coordinator privilege" | `GRANT COORDINATOR_READ` or `GRANT COORDINATOR_WRITE` to the role. | +| `neo4j://` fails but `bolt://` to MAIN works | The role exists on the data instances but not on the coordinators, or it lacks `COORDINATOR_READ`. | +| A session was working and now every query is denied | Its roles were revoked or dropped β€” privileges are re-checked on every query. Reconnect after fixing the grant. | + + diff --git a/pages/clustering/high-availability/ha-commands-reference.mdx b/pages/clustering/high-availability/ha-commands-reference.mdx index 8abb5f194..434aae715 100644 --- a/pages/clustering/high-availability/ha-commands-reference.mdx +++ b/pages/clustering/high-availability/ha-commands-reference.mdx @@ -37,6 +37,17 @@ warning notification instead of a degraded result β€” see [Error handling](#error-handling). + +From Memgraph 3.13, coordinators can enforce privileges on these queries. Every +query on this page requires either `COORDINATOR_READ` (read-only introspection) +or `COORDINATOR_WRITE` (everything mutating). A session that connected with +basic auth carries full `COORDINATOR_WRITE`, so nothing changes unless you +enable [SSO on coordinators](/clustering/high-availability/coordinator-authentication). +See the [privilege +reference](/clustering/high-availability/coordinator-authentication#which-privilege-each-query-requires) +for the per-query mapping. + + ### `ADD COORDINATOR` Adds a coordinator to the cluster. @@ -368,7 +379,9 @@ informational notification that the request was submitted. - Failover of data instances is not triggered, but the new leader recomputes cluster state, so a cluster that was already missing a MAIN can fail over as part of the leadership change. -- No privilege is required to run this query on the coordinators. +- Requires `COORDINATOR_WRITE`. A basic-auth session carries it implicitly; an + [SSO session](/clustering/high-availability/coordinator-authentication) needs + a role that has been granted it. {

Typical use cases

} @@ -696,7 +709,11 @@ survives coordinator restarts and leader re-elections, and is honored across failovers: a newly promoted MAIN comes up read-only when the cluster is in read-only mode, instead of silently accepting writes. -No privilege is required to run this query on the coordinators. +`SET COORDINATOR SETTING` requires `COORDINATOR_WRITE` and `SHOW COORDINATOR +SETTINGS` requires `COORDINATOR_READ`. A basic-auth session carries both +implicitly; an [SSO +session](/clustering/high-availability/coordinator-authentication) needs a role +that has been granted them. Enabling read-only mode blocks **all write sources** on the MAIN β€” user Cypher @@ -718,10 +735,170 @@ cluster self-heals to the requested state. +## Coordinator role and privilege management + + +These queries are **Memgraph Enterprise** features and require a valid license. +They exist so that [SSO +identities](/clustering/high-availability/coordinator-authentication) have +something to map onto. Coordinators have no users β€” only roles. + + +Coordinator roles are stored in the **Raft-replicated cluster state**, not in +the auth store, so they survive restarts, follower catch-up and leader failover. +Like the cluster registration queries, all of these can be run on any +coordinator: writes are transparently forwarded to the leader and committed +through the Raft log, and `SHOW ROLES` / `SHOW PRIVILEGES FOR ROLE` are strong +reads served by the leader. + +### `CREATE ROLE` + +Creates a coordinator role. New roles start with **no** privileges. + +```cypher +CREATE ROLE ifNotExists? roleName; +``` + +{

Behavior & implications

} + +- Errors if the role already exists, unless `IF NOT EXISTS` is given. +- The role name must match the `--auth-user-or-role-name-regex` pattern, + otherwise the query fails with `Invalid role name ''.` +- Requires `COORDINATOR_WRITE`. + +{

Example

} + +```cypher +CREATE ROLE dba; +CREATE ROLE IF NOT EXISTS analyst; +``` + +### `DROP ROLE` + +Removes a coordinator role. + +```cypher +DROP ROLE roleName; +``` + +{

Behavior & implications

} + +- Errors with `Role '' doesn't exist.` if the role is not present. +- Takes effect on **already-connected sessions immediately** β€” privileges are + re-derived from the committed role set on every query, so a session that + authenticated with the dropped role is denied its next privileged query + without needing to reconnect. +- Requires `COORDINATOR_WRITE`. + +{

Example

} + +```cypher +DROP ROLE analyst; +``` + +### `SHOW ROLES` + +Lists the coordinator roles. Returns one `role` column, name only. + +```cypher +SHOW ROLES; +``` + +{

Behavior & implications

} + +- Strong read served by the leader. If no leader can be reached, the query fails + rather than returning possibly-stale local state. +- Requires `COORDINATOR_READ`. + +### `GRANT` / `REVOKE` coordinator privileges + +Grants or revokes a coordinator privilege on a role. Coordinators support +exactly two privileges: `COORDINATOR_READ` and `COORDINATOR_WRITE`, where +`COORDINATOR_WRITE` is a superset of `COORDINATOR_READ`. + +```cypher +GRANT ( ALL PRIVILEGES | COORDINATOR_READ | COORDINATOR_WRITE [, ...] ) TO ROLE? roleName; +REVOKE ( ALL PRIVILEGES | COORDINATOR_READ | COORDINATOR_WRITE [, ...] ) FROM ROLE? roleName; +``` + +{

Behavior & implications

} + +- `GRANT ALL PRIVILEGES` grants **both** coordinator privileges; + `REVOKE ALL PRIVILEGES` removes both. +- Errors with `Role '' doesn't exist.` if the role is not present. +- Only `COORDINATOR_READ` and `COORDINATOR_WRITE` may appear in the privilege + list. Any other privilege, `DENY` in any form, a `USER` target, fine-grained + access control (`ON NODES` / `ON EDGES`), property permissions and + `GRANT DATABASE` are all rejected on a coordinator. +- Like `DROP ROLE`, a `REVOKE` applies to already-connected sessions on their + next query. +- Requires `COORDINATOR_WRITE`. + +{

Example

} + +```cypher +GRANT ALL PRIVILEGES TO dba; +GRANT COORDINATOR_READ TO analyst; +REVOKE COORDINATOR_WRITE FROM analyst; +``` + +### `SHOW PRIVILEGES FOR ROLE` + +Reports the privileges granted to a coordinator role, one per row. + +```cypher +SHOW PRIVILEGES FOR ROLE? roleName; +``` + +{

Behavior & implications

} + +- A role with no grants returns no rows. +- The trailing `ON MAIN | CURRENT | DATABASE ` clause is **rejected** β€” + coordinators have no databases. +- `SHOW PRIVILEGES FOR USER ` is rejected β€” coordinators have no users. +- Strong read served by the leader. +- Requires `COORDINATOR_READ`. + +{

Example

} + +```cypher +SHOW PRIVILEGES FOR ROLE dba; +``` + +```plaintext ++---------------------+ +| privilege | ++---------------------+ +| COORDINATOR_READ | +| COORDINATOR_WRITE | ++---------------------+ +``` + +### `SHOW CURRENT USER` and `SHOW CURRENT ROLE` + +Report the identity of the current session. + +```cypher +SHOW CURRENT USER; +SHOW CURRENT ROLE; +``` + +{

Behavior & implications

} + +- **No privilege and no license are required** β€” these are self-service queries + that reveal only the session's own identity. +- `SHOW CURRENT USER` returns the principal the SSO module reported. It is + session-local and works even when the leader is unreachable. A basic-auth + passthrough session returns `null`. +- `SHOW CURRENT ROLE` returns the session's roles filtered against the leader's + committed role set, so a dropped role stops being reported. A basic-auth + passthrough session has no roles and returns `null`. + ## Error handling If a Raft log commit fails for any cluster operation (register, unregister, -promote, demote, add coordinator), the error message will indicate: +promote, demote, add coordinator, role or privilege change), the error message +will indicate: > Writing to Raft log failed. Please retry the operation. diff --git a/pages/clustering/high-availability/how-high-availability-works.mdx b/pages/clustering/high-availability/how-high-availability-works.mdx index dcdcc4ce2..822a5539b 100644 --- a/pages/clustering/high-availability/how-high-availability-works.mdx +++ b/pages/clustering/high-availability/how-high-availability-works.mdx @@ -116,9 +116,9 @@ The COORDINATOR instance is a **very restricted instance**, and it will not respond to any queries that are not related to management of the cluster. That means, you cannot run any data queries on the coordinator directly (we will talk more about routing data queries in the next sections). However, -system information queries such as `SHOW CONFIG`, `SHOW LICENSE INFO`, -`SHOW BUILD INFO` and `SHOW STORAGE INFO` are supported on coordinators, as -well as `SET DATABASE SETTING`, `RELOAD BOLT_SERVER TLS` and +system information queries such as `SHOW CONFIG`, `SHOW VERSION`, +`SHOW LICENSE INFO`, `SHOW BUILD INFO` and `SHOW STORAGE INFO` are supported on +coordinators, as well as `SET DATABASE SETTING`, `RELOAD BOLT_SERVER TLS` and `RELOAD INTRA_CLUSTER TLS`. @@ -257,24 +257,109 @@ in the cluster to ensure high availability, with timeouts. | RPC message request | source | target | timeout | |--------------------------|-------------|----------------| -----------------| -| `ShowInstancesReq` | Coordinator | Coordinator | | -| `DemoteMainToReplicaReq` | Coordinator | Data instance | | -| `PromoteToMainReq` | Coordinator | Data instance | | -| `RegisterReplicaOnMainReq` | Coordinator | Data instance | | -| `UnregisterReplicaReq` | Coordinator | Data instance | | -| `EnableWritingOnMainReq` | Coordinator | Data instance | deprecated | -| `GetDatabaseHistoriesReq` | Coordinator | Data instance | | +| `ShowInstancesReq` | Coordinator | Coordinator | 10s | +| `DemoteMainToReplicaReq` | Coordinator | Data instance | 10s | +| `PromoteToMainReq` | Coordinator | Data instance | 10s | +| `RegisterReplicaOnMainReq` | Coordinator | Data instance | 10s | +| `UnregisterReplicaReq` | Coordinator | Data instance | 10s | +| `ReplicationLagReq` | Coordinator | Data instance | 5s | +| `GetDatabaseHistoriesReq` | Coordinator | Data instance | 10s | | `StateCheckReq` | Coordinator | Data instance | 5s | -| `SwapMainUUIDReq` | Coordinator | Data instance | | +| `SwapMainUUIDReq` | Coordinator | Data instance | 10s | +| `UpdateDataInstanceConfigReq` | Coordinator | Data instance | 10s | | `FrequentHeartbeatReq` | Main | Replica | 5s | -| `HeartbeatReq` | Main | Replica | | -| `SystemRecoveryReq` | Main | Replica | 5s | +| `HeartbeatReq` | Main | Replica | 10s | +| `SystemRecoveryReq` | Main | Replica | 30s | | `PrepareCommitRpc` | Main | Replica | proportional | | `FinalizeCommitReq` | Main | Replica | 10s | | `SnapshotRpc` | Main | Replica | proportional | | `WalFilesRpc` | Main | Replica | proportional | | `CurrentWalRpc` | Main | Replica | proportional | + +`EnableWritingOnMainReq` was **removed in Memgraph 3.13**. It was never sent β€” +writing on a newly promoted MAIN is enabled through the `writing_enabled` flag +carried inside `PromoteToMainRpc`. Its Prometheus counters were removed along +with it. + + +{
Follower-to-leader forwarding timeouts
} + +Cluster management queries can be run on any coordinator; a follower forwards +them to the leader over RPC. From Memgraph 3.13, each of these RPCs has an +explicit timeout. They run on the caller's **Bolt session thread**, so without +one, a session would block forever against a leader that is reachable but stuck. + +The four instance operations must outlast the work they trigger on the leader, +or a follower would report failure for an operation the leader has already +committed to Raft. Their budgets are the sum of that work plus headroom, where a +Raft commit is capped at 3 seconds and each RPC from the leader to a data +instance is capped by its entry in the table above. + +| RPC message request | source | target | timeout | Budget breakdown | +|--------------------------|-------------|-------------|---------|------------------| +| `RegisterInstanceReq` | Coordinator | Coordinator | 30s | Raft commit + demote the new replica + register it | +| `UnregisterInstanceReq` | Coordinator | Coordinator | 20s | Raft commit + one RPC to the current MAIN | +| `DemoteInstanceReq` | Coordinator | Coordinator | 20s | Raft commit + one RPC to the current MAIN | +| `SetInstanceToMainReq` | Coordinator | Coordinator | 60s | Raft commit + one `SwapMainUUID` per other instance + promote the new MAIN | +| `AddCoordinatorReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `RemoveCoordinatorReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `UpdateConfigReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `ForceResetReq` | Coordinator | Coordinator | 60s | Unbounded leader-side work β€” see the note below | +| `SetCoordinatorSettingReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `GetRoutingTableReq` | Coordinator | Coordinator | 10s | Read served by the leader | +| `CoordReplLagReq` | Coordinator | Coordinator | 10s | Read served by the leader | +| `CreateRoleReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `DropRoleReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `GrantPrivilegeReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `RevokePrivilegeReq` | Coordinator | Coordinator | 10s | Raft commit only | +| `GetRolesReq` | Coordinator | Coordinator | 10s | Read served by the leader | +| `GetRolePrivilegesReq` | Coordinator | Coordinator | 10s | Read served by the leader | + + +`SetInstanceToMainReq` sends one `SwapMainUUID` per other instance, so its cost +grows with the number of data instances. The 60s budget comfortably covers five +instances. Beyond that, a follower can time out before the leader answers β€” no +fixed value bounds it. Run `SET INSTANCE ... TO MAIN` directly on the leader in +very large clusters. + +`ForceResetReq` triggers a reconciliation that retries under a 1s–60s backoff +for as long as the coordinator stays leader, so the leader-side work has no +upper bound at all. The 60s budget only keeps a genuinely wedged leader from +blocking the session β€” hitting it does **not** mean the reset failed, and +[`FORCE RESET CLUSTER +STATE`](/clustering/high-availability/ha-commands-reference#force-reset-cluster-state) +is safe to re-run. + + +The role and privilege RPCs are used by [coordinator +authentication](/clustering/high-availability/coordinator-authentication): +`GetRolesReq` in particular is sent on **every query of an SSO session**, because +coordinator privileges are re-derived from the leader's committed role set +rather than cached at login. + +{
System transaction timeouts
} + +MAIN-to-REPLICA system-delta RPCs are sent while committing a system +transaction, so they must not block indefinitely either. From Memgraph 3.13 each +carries a **10 second** timeout; a timeout marks the REPLICA as `BEHIND` and +defers to system recovery. + +| RPC message request | source | target | timeout | +|---------------------|--------|---------|---------| +| `UpdateAuthDataReq` | Main | Replica | 10s | +| `DropAuthDataReq` | Main | Replica | 10s | +| `FinalizeSystemTxReq` | Main | Replica | 10s | +| `CreateDatabaseReq` | Main | Replica | 10s | +| `DropDatabaseReq` | Main | Replica | 10s | +| `SuspendDatabaseReq` | Main | Replica | 10s | +| `ResumeDatabaseReq` | Main | Replica | 10s | +| `RenameDatabaseReq` | Main | Replica | 10s | +| `TenantProfileReq` | Main | Replica | 10s | +| `SetParameterReq` | Main | Replica | 10s | +| `UnsetParameterReq` | Main | Replica | 10s | +| `DeleteAllParametersReq`| Main | Replica | 10s | + ## Intra-cluster TLS By default, the communication between instances in a high-availability cluster diff --git a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx index b9275c9b2..7a91fb7ab 100644 --- a/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx +++ b/pages/clustering/high-availability/querying-the-cluster-in-high-availability.mdx @@ -190,21 +190,51 @@ When using the **bolt+routing protocol**, provide credentials for users that exist on the data instances. The authentication flow works as follows: 1. The client authenticates and connects to a **coordinator**. -2. The coordinator returns a routing table - no authentication occurs here. +2. The coordinator returns a routing table. 3. The client connects to the appropriate **data instance** using the **same credentials**. 4. The data instance performs authentication and executes the query. -This preserves a clean separation: coordinators route traffic, while data -instances manage users. - -1. You may connect to a coordinator via plain Bolt **without authentication**. -2. When using Bolt+routing, you **must** provide credentials - authentication is +1. By default, you may connect to a coordinator via plain Bolt **without + authentication** β€” username and password are accepted as a passthrough and + ignored. +2. When using Bolt+routing, you **must** provide credentials β€” authentication is performed on the data instances. +### Authenticating against coordinators + +From Memgraph 3.13, coordinators are no longer unconditionally open. They can +enforce [single sign-on and coordinator +privileges](/clustering/high-availability/coordinator-authentication), which +changes the flow above in two ways: + +- **Step 1 becomes a real authentication.** Once SSO is configured on the + coordinators, basic auth is refused and the client must present a valid IdP + token for a scheme listed in `--auth-module-mappings`. +- **Step 2 requires a privilege.** Serving the routing table requires + `COORDINATOR_READ`. A session whose roles carry no coordinator privilege is + denied the routing table with `You don't have permission to read the routing + table on the coordinator!` β€” reported as a non-retryable client error so + drivers do not retry it. + + + +Because the driver reuses **one set of credentials for both legs** of a routing +connection, a `neo4j://` session works only when the **same role names exist on +both the coordinators and the data instances**, and the same SSO scheme is +configured on both. The privileges attached to those roles differ by design β€” +coordinators know only `COORDINATOR_READ` / `COORDINATOR_WRITE`, data instances +know only the data privilege set β€” but the names must match. + +See [Bolt+routing with +SSO](/clustering/high-availability/coordinator-authentication#boltrouting-with-sso) +for the full setup. + + + diff --git a/pages/database-management/authentication-and-authorization/auth-system-integrations.mdx b/pages/database-management/authentication-and-authorization/auth-system-integrations.mdx index db2a1eb4c..252430ec0 100644 --- a/pages/database-management/authentication-and-authorization/auth-system-integrations.mdx +++ b/pages/database-management/authentication-and-authorization/auth-system-integrations.mdx @@ -154,6 +154,29 @@ This approach ensures that all roles are created before the external authentication module is activated, allowing users to log in seamlessly across all supported authentication methods. +This workflow only applies to standalone Memgraph instances. On [data instances +running in HA +mode](/clustering/high-availability/how-high-availability-works#data-instance-implementation) +(i.e. when `--management-port` is set), `--init-file` is **not supported** and +the instance will fail to start if the flag is provided. There you must use the +manual procedure instead: start the data instance **without** +`--auth-module-mappings`, log in, create the roles and grant their privileges, +then restart with the flag set. + + +From Memgraph 3.13, [high availability +coordinators](/clustering/high-availability/coordinator-authentication) also +support SSO, using these same modules, schemes and `MEMGRAPH_SSO_*` environment +variables. Coordinators **do not** need this stop-and-restart cycle: basic +authentication stays open until a role holding `COORDINATOR_WRITE` exists, so +you can start them with `--auth-module-mappings` from the beginning and create +the roles over Bolt on the running cluster. + +Note that a `neo4j://` routing connection authenticates against a coordinator +*and* a data instance with the same credentials, so the **same role names must +exist on both**. + + ## Auth module architecture ### Communication protocol diff --git a/pages/database-management/authentication-and-authorization/query-privileges.mdx b/pages/database-management/authentication-and-authorization/query-privileges.mdx index 0e707cd3f..b2fcdba03 100644 --- a/pages/database-management/authentication-and-authorization/query-privileges.mdx +++ b/pages/database-management/authentication-and-authorization/query-privileges.mdx @@ -143,7 +143,7 @@ Memgraph's privilege system controls access to various database operations throu | `SHOW SNAPSHOTS` | `DURABILITY` | `SHOW SNAPSHOTS` | | `SHOW NEXT SNAPSHOT` | `DURABILITY` | `SHOW NEXT SNAPSHOT` | | `SET SETTING` | `CONFIG` | `SET SETTING ...` | -| `SHOW VERSION` | `STATS` | `SHOW VERSION` | +| `SHOW VERSION` | `STATS` | `SHOW VERSION` β€” on coordinators it requires `COORDINATOR_READ` instead, see [Coordinator operations](#coordinator-operations). | | `SHOW TRANSACTIONS` | `TRANSACTION_MANAGEMENT` | `SHOW TRANSACTIONS` | | `TERMINATE TRANSACTIONS` | `TRANSACTION_MANAGEMENT` | `TERMINATE TRANSACTIONS 'transaction_id'` | | `TERMINATE TRANSACTIONS "*"` | `TRANSACTION_MANAGEMENT` | `TERMINATE TRANSACTIONS "*"` terminates every transaction the user may terminate. Without the privilege, only the user's own transactions are terminated. | @@ -157,7 +157,7 @@ Memgraph's privilege system controls access to various database operations throu | `REPLICATION` operations | `REPLICATION` | Various replication commands. | | `SHOW REPLICATION ROLE` | `REPLICATION` | `SHOW REPLICATION ROLE` | | `SHOW REPLICAS` | `REPLICATION` | `SHOW REPLICAS` | -| `SHOW REPLICATION LAG` | `COORDINATOR` | `SHOW REPLICATION LAG` | +| `SHOW REPLICATION LAG` | `COORDINATOR_READ` | Coordinators only β€” see [Coordinator operations](#coordinator-operations). | ## Multi-database operations @@ -192,12 +192,40 @@ Memgraph's privilege system controls access to various database operations throu ## Coordinator operations +These privileges apply **only on [high availability +coordinators](/clustering/high-availability/coordinator-authentication)**. They +are granted to roles that live in the coordinators' Raft-replicated cluster +state, not in the data-instance auth store, and are not part of the +data-instance privilege set β€” `GRANT ALL PRIVILEGES` on a data instance does not +grant them. + +`COORDINATOR_WRITE` is a superset of `COORDINATOR_READ`: a `COORDINATOR_WRITE` +grant satisfies a `COORDINATOR_READ` requirement. A session that connected to a +coordinator with basic auth carries both implicitly. + | Query Type | Required Privileges | Example | |------------|-------------------|---------| +| Routing table (Bolt `ROUTE` message) | `COORDINATOR_READ` | Any `neo4j://` connection. | +| `SHOW INSTANCE` / `SHOW INSTANCES` | `COORDINATOR_READ` | `SHOW INSTANCES` | +| `SHOW COORDINATOR SETTINGS` | `COORDINATOR_READ` | `SHOW COORDINATOR SETTINGS` | +| `SHOW REPLICATION LAG` | `COORDINATOR_READ` | `SHOW REPLICATION LAG` | +| `SHOW ROLES` / `SHOW PRIVILEGES FOR ROLE` | `COORDINATOR_READ` | `SHOW PRIVILEGES FOR ROLE dba` | +| `SHOW CONFIG`, `SHOW SETTING`, system info queries | `COORDINATOR_READ` | `SHOW CONFIG` | +| `SHOW VERSION` | `COORDINATOR_READ` | `SHOW VERSION` | +| `SHOW CURRENT USER` / `SHOW CURRENT ROLE` | **None** | Self-service identity queries. | +| Cluster management operations | `COORDINATOR_WRITE` | `REGISTER INSTANCE`, `SET INSTANCE ... TO MAIN`, `ADD COORDINATOR`, `YIELD LEADERSHIP`, `FORCE RESET CLUSTER STATE`, … | +| `SET COORDINATOR SETTING` / `SET SETTING` | `COORDINATOR_WRITE` | `SET COORDINATOR SETTING 'sync_failover_only' TO 'true'` | +| `CREATE ROLE` / `DROP ROLE` / `GRANT` / `REVOKE` | `COORDINATOR_WRITE` | `GRANT COORDINATOR_READ TO analyst` | | `COORDINATOR` operations | `COORDINATOR` | Various coordinator commands. | | `SHOW COORDINATOR SETTINGS` | `COORDINATOR` | `SHOW COORDINATOR SETTINGS` | | `SHOW ROUTING TABLE` | `COORDINATOR` | `SHOW ROUTING TABLE` | + +**Breaking change in Memgraph 3.13:** the `COORDINATOR` privilege was removed +and replaced by `COORDINATOR_READ` and `COORDINATOR_WRITE`. It never gated any +operation and is no longer accepted in `GRANT` / `DENY` / `REVOKE` statements. + + ## Schema information | Query Type | Required Privileges | Example | diff --git a/pages/database-management/authentication-and-authorization/role-based-access-control.mdx b/pages/database-management/authentication-and-authorization/role-based-access-control.mdx index cc2259042..d5ddaf21a 100644 --- a/pages/database-management/authentication-and-authorization/role-based-access-control.mdx +++ b/pages/database-management/authentication-and-authorization/role-based-access-control.mdx @@ -243,7 +243,6 @@ of the following commands: | Privilege to change [storage mode](/fundamentals/storage-memory-usage#storage-modes). | `STORAGE_MODE` | | Privilege to manage [multi-tenant databases](/database-management/multi-tenancy). | `MULTI_DATABASE_EDIT` | | Privilege to use a database within the multi-tenant architecture. | `MULTI_DATABASE_USE` | -| Privilege to configure [high-availability](/clustering/high-availability) coordinators. | `COORDINATOR` | | Privilege to [impersonate other users](/database-management/authentication-and-authorization/impersonate-user). | `IMPERSONATE_USER` | | Privilege to use [parallel execution](/querying/parallel-execution). | `PARALLEL_EXECUTION` | | Privilege to set limits and monitor resource usage per user (via [user profiles](/database-management/authentication-and-authorization/user-profiles)) or per database (via [tenant profiles](/database-management/tenant-profiles)). | `PROFILE_RESTRICTION` | @@ -257,6 +256,22 @@ of the following commands: For a comprehensive reference of which privileges are required for specific queries and operations, see the [Query privileges reference](/database-management/authentication-and-authorization/query-privileges) documentation. + +**Breaking change in Memgraph 3.13:** the `COORDINATOR` privilege was removed β€” +it never gated any operation. It is no longer accepted in `GRANT` / `DENY` / +`REVOKE` statements and is no longer reported by `SHOW PRIVILEGES`. A stale +`COORDINATOR` grant in an existing deployment is never reported and never +checked, so no migration is needed. + +Access to [high availability](/clustering/high-availability) coordinators is now +controlled by the `COORDINATOR_READ` and `COORDINATOR_WRITE` privileges. These +are **coordinator-only**: they are granted to roles that live on the +coordinators themselves, they are not part of the data-instance privilege set, +and `GRANT ALL PRIVILEGES` on a data instance does not grant them. See +[coordinator +authentication](/clustering/high-availability/coordinator-authentication). + + ### First user privileges When you create the first user in Memgraph, that user automatically becomes a diff --git a/pages/database-management/enabling-memgraph-enterprise.mdx b/pages/database-management/enabling-memgraph-enterprise.mdx index 0c33f655a..5dc2ac744 100644 --- a/pages/database-management/enabling-memgraph-enterprise.mdx +++ b/pages/database-management/enabling-memgraph-enterprise.mdx @@ -245,6 +245,12 @@ ideal solution for those who need a worry-free, highly available system with failover ensures your system remains operational with minimal downtime and manual intervention. +The cluster's control plane can also be secured: [coordinator +authentication](/clustering/high-availability/coordinator-authentication) lets +coordinators authenticate OIDC, SAML and Kerberos connections against your +identity provider and enforce the `COORDINATOR_READ` and `COORDINATOR_WRITE` +privileges on Raft-replicated roles. + ### Multi-tenancy [Multi-tenant support](/database-management/multi-tenancy) enables you to manage diff --git a/pages/database-management/server-stats.mdx b/pages/database-management/server-stats.mdx index 51a8c2c19..c3ffc364f 100644 --- a/pages/database-management/server-stats.mdx +++ b/pages/database-management/server-stats.mdx @@ -18,6 +18,13 @@ To get the version of the instance being queried, run the following query: SHOW VERSION; ``` +The query can be run on data instances and on +[coordinators](/clustering/high-availability), so you can check the version of +every instance in a high-availability cluster without connecting through the +MAIN. On data instances it requires the `STATS` privilege; on coordinators it is +a read-only query that requires +[`COORDINATOR_READ`](/clustering/high-availability/coordinator-authentication#which-privilege-each-query-requires). + ## Storage information `SHOW STORAGE INFO` comes in two flavors: diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 7592c5798..fdb026d2c 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -58,6 +58,23 @@ guide. you need the previous Python behavior, or update consumers for the new structured / ISO-8601 JSON forms. [#4443](https://github.com/memgraph/memgraph/pull/4443) +- The `COORDINATOR` privilege was removed β€” it never gated any operation. It is + no longer accepted in `GRANT` / `DENY` / `REVOKE` statements, which now fail + with a syntax error, and it is no longer reported by `SHOW PRIVILEGES` or + included in `GRANT ALL PRIVILEGES`. Remove any `GRANT COORDINATOR` / + `DENY COORDINATOR` / `REVOKE COORDINATOR` statements from provisioning scripts + and update tooling that compares against the full privilege list. No data + migration is required: a stale grant in an existing deployment is never + reported and never checked. The new `COORDINATOR_READ` and `COORDINATOR_WRITE` + privileges are not a replacement β€” they are coordinator-only and are rejected + on data instances. + [#4399](https://github.com/memgraph/memgraph/pull/4399) +- Connecting to a coordinator with a username and password is no longer always + accepted. Once `--auth-module-mappings` is set, the enterprise license is + valid, **and** a role holding `COORDINATOR_WRITE` exists, basic authentication + is denied in favor of SSO. Until all three hold, the previous passthrough + behavior is unchanged. + [#4399](https://github.com/memgraph/memgraph/pull/4399) - `SHOW INSTANCES` is now strongly consistent and always answered by the leader coordinator. A follower that cannot reach the leader no longer falls back to reporting the cluster from its own local Raft state with `unknown` health β€” @@ -146,6 +163,15 @@ guide. statements). Under index-heavy workloads this removes GC-correlated latency spikes on index and constraint creation. [#4468](https://github.com/memgraph/memgraph/pull/4468) +- Cluster management queries forwarded from a follower coordinator to the leader + now have [explicit RPC + timeouts](/clustering/high-availability/how-high-availability-works#rpc-timeouts), + so a Bolt session can no longer block indefinitely against a leader that is + reachable but stuck. The same applies to MAIN-to-REPLICA system transaction + messages (auth, multi-tenancy, parameters), which now time out after 10s and + defer to system recovery. The unused `EnableWritingOnMainRpc` and its + Prometheus counters were removed. + [#4399](https://github.com/memgraph/memgraph/pull/4399) - `YIELD LEADERSHIP` and `SHOW COORDINATOR SETTINGS` can now be run on any coordinator β€” followers forward them to the leader instead of failing or answering from local state. When no leader can serve a cluster query, @@ -189,6 +215,24 @@ guide. - `CREATE RANGE INDEX FOR ... ON ...` now works for nodes and relationships and creates Memgraph’s usual property index. [#4486](https://github.com/memgraph/memgraph/pull/4486) +- [SSO authentication on high availability + coordinators](/clustering/high-availability/coordinator-authentication) + (Enterprise). Coordinators authenticate OIDC, SAML and Kerberos connections + against a Raft-replicated set of roles and enforce two privileges, + `COORDINATOR_READ` and `COORDINATOR_WRITE`. Manage them with `CREATE ROLE`, + `DROP ROLE`, `SHOW ROLES`, `GRANT` / `REVOKE COORDINATOR_READ | + COORDINATOR_WRITE | ALL PRIVILEGES`, and `SHOW PRIVILEGES FOR ROLE` on any + coordinator β€” follower queries are forwarded to the leader. Basic + authentication stays open until a `COORDINATOR_WRITE` role exists, so no + coordinator restart is needed to roll SSO out, and it reopens if the license + becomes invalid so a coordinator can never lock every session out. + [#4399](https://github.com/memgraph/memgraph/pull/4399) +- `SHOW VERSION` can now be run on coordinators, alongside the other read-only + introspection queries such as `SHOW CONFIG` and `SHOW BUILD INFO`. It + previously failed with `Coordinator can run only coordinator queries!`. On + coordinators it is a `COORDINATOR_READ` query, so an SSO role with only + `COORDINATOR_READ` granted can run it. + [#4535](https://github.com/memgraph/memgraph/pull/4535) - Added the `SHOW ROUTING TABLE` query, which shows the routing table a coordinator hands out to `bolt+routing` clients as `WRITE`, `READ` and `ROUTE` rows. It can only be run on a coordinator, is always answered from the From 7ed2793b2b2aaa7efeed101daf32bd4fa9fdd7fa Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Wed, 12 Aug 2026 14:09:12 +0200 Subject: [PATCH 19/19] docs: Durability changes (#1723) Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- pages/fundamentals/data-durability.mdx | 40 +++++++++++++++++++++----- pages/release-notes.mdx | 19 ++++++++++++ 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/pages/fundamentals/data-durability.mdx b/pages/fundamentals/data-durability.mdx index 449dfc644..46a8410dc 100644 --- a/pages/fundamentals/data-durability.mdx +++ b/pages/fundamentals/data-durability.mdx @@ -103,6 +103,11 @@ Per-transaction WAL checksums were introduced in Memgraph v3.12. WAL files written by older versions do not contain checksums and are recovered without integrity verification. Checksum verification applies only to WAL files written by Memgraph v3.12 or newer. + +Before Memgraph v3.13, a checksum mismatch anywhere in the WAL chain recovered +the readable prefix of that file and then continued with the files after it, +which could bring the database up with an incomplete dataset without reporting +it. Since v3.13, damage in a completed WAL file fails recovery instead. To guard against silent on-disk corruption, Memgraph protects WAL files with @@ -113,16 +118,37 @@ To guard against silent on-disk corruption, Memgraph protects WAL files with - **Each transaction** is protected by a 4-byte checksum covering the transaction's bytes (transaction start, deltas and transaction end). -Checksums are verified automatically during recovery. If a transaction's stored -checksum does not match the recomputed value, the WAL is considered corrupted at -that point and recovery stops, so corrupted data is never applied to the -database. A mismatch in the WAL header causes recovery from that file to fail. +Checksums are verified automatically during recovery, so corrupted data is never +applied to the database. A mismatch in the WAL header causes recovery from that +file to fail. What happens on a transaction checksum mismatch depends on whether +the file was completed: + +- A **completed (finalized) WAL file** β€” one Memgraph finished writing and + rotated away from β€” was flushed to disk before it was closed, so every + transaction in it is durable and was already acknowledged. A mismatch there + means the bytes on disk rotted, and recovery fails rather than continuing with + part of the file. Recovering only a prefix would be unsound, because the WAL + files that follow were written on top of the transactions that went missing. +- A **WAL file that was still being written** when the process stopped may + legitimately have a torn tail. Recovery applies its transactions up to the + last whole one and stops there, which is how an interrupted write degrades + gracefully. + +Files that the newest snapshot already covers are skipped without being read, so +corruption in those is harmless. If a WAL file written after the newest snapshot +is damaged, the database fails to recover; use +[`--storage-allow-recovery-failure`](/database-management/configuration) together +with `RECOVER SNAPSHOT` (see [recovery failure +handling](#recovery-failure-handling)), or restore from a backup. The same checksums protect WAL files that are buffered on disk on a replica before being applied, so corruption introduced between the main and the replica -is detected before the data is committed. Deltas streamed during the commit -(`PrepareCommitRpc`) are not checksummed because the TCP transport already -provides integrity guarantees. +is detected before the data is committed. When a replica hits damage in a +finalized file it reports the failure and stops applying the rest of the batch +instead of skipping ahead; the in-flight transaction is aborted, main does not +advance its view of the replica, and the transfer is retried later. Deltas +streamed during the commit (`PrepareCommitRpc`) are not checksummed because the +TCP transport already provides integrity guarantees. Snapshots are not yet protected by checksums. diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index fdb026d2c..43dacaa9b 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -133,6 +133,19 @@ guide. - Fixed a crash when multiple sessions printed query plans at the same time (for example concurrent `EXPLAIN` / `PROFILE`, or query-plan logging). [#4489](https://github.com/memgraph/memgraph/pull/4489) +- Fixed silent data loss when a WAL file other than the last one in the chain + was damaged. Recovery used to apply the readable prefix of that file and then + apply the following files in full, coming up with an incomplete dataset + without reporting anything; on a replica the divergence never healed because + the replica reported the later timestamp and main considered it caught up. + Damage in a completed (finalized) WAL file is now fatal: the database fails + recovery instead of starting stale, and a replica stops applying the rest of + the chain. A WAL file that was still being written when the process crashed + is unaffected and is still truncated to its last whole transaction. Recover + such an instance with + [`--storage-allow-recovery-failure`](/database-management/configuration) plus + `RECOVER SNAPSHOT`, or from a backup. + [#4528](https://github.com/memgraph/memgraph/pull/4528) - `TERMINATE TRANSACTIONS` no longer reports `killed: true` for a transaction the caller is not authorized to terminate. Such a match now reports `killed: false`, indistinguishable from an id that does not exist. @@ -163,6 +176,12 @@ guide. statements). Under index-heavy workloads this removes GC-correlated latency spikes on index and constraint creation. [#4468](https://github.com/memgraph/memgraph/pull/4468) +- Recovery from WAL files is roughly 35% faster. Each WAL file now records its + timestamp range and transaction count in its header, so deciding which files + to recover from no longer requires parsing every file end to end, and + replaying a file parses it once instead of twice. This also speeds up replica + recovery and the WAL cleanup that runs after every snapshot. + [#4528](https://github.com/memgraph/memgraph/pull/4528) - Cluster management queries forwarded from a follower coordinator to the leader now have [explicit RPC timeouts](/clustering/high-availability/how-high-availability-works#rpc-timeouts),