From 8256a0d59a23d3cd70fe793acf474717626bcd8d Mon Sep 17 00:00:00 2001 From: Vlasta Date: Tue, 15 Sep 2026 09:33:01 +0200 Subject: [PATCH 01/13] Add Memgraph v3.14.0 and Lab v3.14.0 release note titles Co-authored-by: Cursor --- 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 7481af8c6..9f4c841dc 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -46,6 +46,14 @@ guide. ## 🚀 Latest release +### Memgraph v3.14.0 - October 21st, 2026 + +### Lab v3.14.0 - October 21st, 2026 + + + +## Previous releases + ### Memgraph v3.13.1 - September 14th, 2026 {

✨ New features

} @@ -71,8 +79,6 @@ guide. -## Previous releases - ### Memgraph v3.13.0 - September 9th, 2026 {

⚠️ Breaking changes

} From f47498021167a1d9e8ebcf1e9fcec3c2a6ab47f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:27:47 +0200 Subject: [PATCH 02/13] docs: the bare pattern shorthand takes what a MATCH pattern takes (#1756) * docs: the bare pattern shorthand takes what a MATCH pattern takes The page stated two rules that no longer hold: that the shorthand's pattern cannot introduce names of its own, and that it holds a pattern and nothing else so a filter needs the `MATCH` form. It now takes a variable it declares itself, a trailing `WHERE`, a comma-separated list, a named path and a lone node. The worked example is the filter the old text used to send to the `MATCH` form, written as the shorthand, with output from the page's own dataset. `COLLECT` still has no shorthand. * docs: a pattern holding only a bound variable matches on its value A pattern that names a variable the query has already bound used to be ignored, so the row passed through whatever the variable held. It is now matched: a node is kept, a null matches nothing and drops the row, and a value that is neither raises. This applies to a top-level MATCH and a CALL {} subquery as well as to a subquery expression body, which the pages did not say. Also documents the rule that came with it: a pattern name inside a subquery expression body may not repeat a name from outside the body. Every output block was produced by running the query against its own page's dataset block. --- pages/querying/clauses/match.mdx | 74 +++++++++++++++++ pages/querying/subquery-expressions.mdx | 103 ++++++++++++++++++++++-- 2 files changed, 170 insertions(+), 7 deletions(-) diff --git a/pages/querying/clauses/match.mdx b/pages/querying/clauses/match.mdx index eeeb97b1f..2cf2f58f5 100644 --- a/pages/querying/clauses/match.mdx +++ b/pages/querying/clauses/match.mdx @@ -30,6 +30,7 @@ The `MATCH` clause is used to obtain data from the database by matching it to a 4. [Using multiple MATCH clauses](#4-using-multiple-match-clauses)
4.1. [Cartesian product of nodes](#41-cartesian-product-of-nodes)
4.2. [Creating a list](#42-creating-a-list)
+ 4.3. [Matching a variable that is already bound](#43-matching-a-variable-that-is-already-bound)
@@ -476,6 +477,79 @@ Output: The `OPTIONAL MATCH` clause bypasses the empty set and the query returns only non-empty sets. Therefore, the output of the query is a list containing only the results of the first `MATCH` clause. +### 4.3. Matching a variable that is already bound + +A pattern may hold nothing but a variable the query has already bound. Such a +pattern does not scan the graph — it matches on the value bound to that name: + +```cypher +MATCH (p:Person) +MATCH (p) +RETURN p.name AS name +ORDER BY name; +``` + +Output: + +```nocopy ++---------+ +| name | ++---------+ +| "Anna" | +| "Harry" | +| "John" | ++---------+ +``` + +A node matches and the row is kept. A `null`, which an `OPTIONAL MATCH` leaves +behind when nothing matched, matches nothing and the row is dropped: + +```cypher +MATCH (p:Person) +OPTIONAL MATCH (p)-[:LIVING_IN]->(c:Country {continent: 'Asia'}) +WITH p, c +MATCH (c) +RETURN p.name AS name, c.name AS country +ORDER BY name; +``` + +Output: + +`Empty set` + +No person lives in an Asian country, so `c` is `null` on every row and `MATCH (c)` +keeps none of them. Without the `MATCH (c)`, the query returns all three people +with a `null` country. + +A value that is neither a node nor `null` raises an error: + +```cypher +MATCH (p:Person) +WITH p.name AS p +MATCH (p) +RETURN p; +``` + +Output: + +```nocopy +Expected a node for 'p', but got string. +``` + + + +This applies wherever a pattern appears — a top-level `MATCH`, a `CALL {}` +subquery, and the body of a [subquery +expression](/querying/subquery-expressions). Before Memgraph 3.14 a pattern +holding only a bound variable was ignored, so the row passed through whatever the +variable held. Queries that carried a `null` or a non-node value through such a +pattern now return fewer rows or raise an error. + +A pattern that also states a label or a property, such as `MATCH (p:Person)`, is +unaffected — it already raised on a non-node value. + + + ## Dataset queries We encourage you to try out the examples by yourself. diff --git a/pages/querying/subquery-expressions.mdx b/pages/querying/subquery-expressions.mdx index 229b931b8..0ee56f896 100644 --- a/pages/querying/subquery-expressions.mdx +++ b/pages/querying/subquery-expressions.mdx @@ -402,9 +402,8 @@ Output: ### The bare pattern shorthand `EXISTS` and `COUNT` accept a bare pattern in place of a full body, which is a -shorter way to write a check or a count over one pattern. The pattern cannot -introduce names of its own — a variable already bound outside may appear in it, -and acts as a join: +shorter way to write a check or a count over one pattern. A variable already +bound outside may appear in it, and acts as a join: ```cypher MATCH (p:Person) @@ -427,10 +426,32 @@ Output: +---------+-------+---------+ ``` -The shorthand holds a pattern and nothing else, so a filter or any other clause -needs the `MATCH` form — `COUNT { MATCH (p)-[r:KNOWS]->() WHERE r.since < 2015 }`. -`COLLECT` has no shorthand, since a bare pattern returns no column for it to -gather. +The shorthand takes everything a `MATCH` pattern takes: a variable it declares +itself, a trailing `WHERE`, several patterns separated by commas, a named path, +and a pattern holding only a node. + +```cypher +MATCH (p:Person) +RETURN p.name AS name, + COUNT { (p)-[r:KNOWS]->(f:Person) WHERE r.since < 2015 } AS early +ORDER BY name; +``` + +Output: + +```nocopy ++---------+-------+ +| name | early | ++---------+-------+ +| "Alice" | 1 | +| "Bob" | 0 | +| "Carol" | 0 | +| "Dave" | 0 | ++---------+-------+ +``` + +Any other clause still needs the `MATCH` form. `COLLECT` has no shorthand, since +a bare pattern returns no column for it to gather. ### Correlation with the enclosing query @@ -479,6 +500,74 @@ Output: Variables introduced inside the body are scoped to it and are not available after the subquery expression. +A pattern in the body may hold nothing but a correlated variable. Such a pattern +matches on the value bound to that name rather than scanning the graph: a node +matches, a `null` matches nothing, and a value that is neither raises an error. + +```cypher +MATCH (p:Person) +OPTIONAL MATCH (p)-[:KNOWS]->(f:Person) +WITH p, f +RETURN p.name AS name, EXISTS { (f) } AS bound +ORDER BY name, bound; +``` + +Output: + +```nocopy ++---------+-------+ +| name | bound | ++---------+-------+ +| "Alice" | true | +| "Alice" | true | +| "Bob" | true | +| "Carol" | false | +| "Dave" | false | ++---------+-------+ +``` + +Carol and Dave know nobody, so `f` is `null` on their rows and `EXISTS { (f) }` +is `false`. This is the same rule a top-level [`MATCH`](/querying/clauses/match) +follows. + +A named path inside the body may not take its name from a variable declared +outside the body. The body writes into the frame it shares with its caller, so +one name cannot mean the caller's value in one place and the body's own path in +another: + +```cypher +MATCH (p:Person) +WITH p, p.name AS path +RETURN COUNT { MATCH path = (p)-[:KNOWS]->() } AS c; +``` + +Output: + +```nocopy +Cannot name a pattern 'path' in COUNT, because that variable is already declared outside it. +``` + +Give the path a name of its own, and it works: + +```cypher +MATCH (p:Person) +RETURN COUNT { MATCH q = (p)-[:KNOWS]->() } AS c +ORDER BY c; +``` + +Output: + +```nocopy ++---+ +| c | ++---+ +| 0 | +| 0 | +| 1 | +| 2 | ++---+ +``` + ### The body's RETURN decides the result The body is a query, and its final `RETURN` shapes the row set that the From 4425c23a114e08a5aab315f79064ece4eeb0170a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:28:35 +0200 Subject: [PATCH 03/13] docs: document the nullIf built-in (#1763) * docs(querying): document the nullIf built-in nullIf(value1, value2) answers null where the two arguments are equal and the first argument otherwise, so it takes a value away where coalesce supplies one. * docs(querying): drop nullIf from the unsupported function list It was the only entry under the scalar functions heading, so the heading goes with it. --- pages/querying/differences-in-cypher-implementations.mdx | 4 ---- pages/querying/functions.mdx | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pages/querying/differences-in-cypher-implementations.mdx b/pages/querying/differences-in-cypher-implementations.mdx index ded8b6460..d51420639 100644 --- a/pages/querying/differences-in-cypher-implementations.mdx +++ b/pages/querying/differences-in-cypher-implementations.mdx @@ -238,10 +238,6 @@ RETURN val, valueType(val) = "INTEGER" - `exists(n.property)` - can be expressed using `n.property IS NOT NULL` - `isEmpty()` -**Scalar functions**: - -- `nullIf()` - **Aggregating functions**: - `percentileCont()`, `percentileDisc()` diff --git a/pages/querying/functions.mdx b/pages/querying/functions.mdx index 62bd67adc..1a816ca14 100644 --- a/pages/querying/functions.mdx +++ b/pages/querying/functions.mdx @@ -50,6 +50,7 @@ This section contains the list of supported functions. | `elementId` | `elementId(value: Node\|Relationship) -> (string)` | Returns the identifier for a given node or relationship as a string. The identifier is the same as the one returned by `id()`. | | `last` | `last(list: List[any]) -> (any)` | Returns the last element of a list. | | `length` | `length(value: List\|string\|Map\|Path) -> (integer)` | Returns the number of elements in the value. When given a **list** it returns the size of the list. When given a string it returns the number of characters. When given a path it returns the number of expansions (relationships) in that path. | + | `nullIf` | `nullIf(value1: any, value2: any) -> (any)` | Returns `null` if the two arguments are equal, and the first argument otherwise. The arguments are compared as the `=` operator compares them, so a comparison that involves a `null` is undecided and the first argument is returned. | | `properties` | `properties(value: Node\|Relationship) -> (Map[string, any])` | Returns the property map of a node or a relationship. | | `propertySize` | `propertySize(entity: Node\|Relationship, property-name: string) -> (integer)` | Returns the total amount of bytes stored in RAM for the property of a given entity node or relationship. For more information, check [storage of properties inside Memgraph](/fundamentals/storage-memory-usage#calculate-storage-memory-usage). | | `randomUUID` | `randomUUID() -> (string)` | Returns randomly-generated Universally Unique Identifier (UUID) | From 4cf8c19e7be759bee64c03774164c3fd31934b7d Mon Sep 17 00:00:00 2001 From: Vlasta Date: Tue, 15 Sep 2026 16:02:05 +0200 Subject: [PATCH 04/13] Add Memgraph v3.14.0 release notes and drop per-release sitemap steps. Co-authored-by: Cursor --- pages/release-notes.mdx | 61 ++++++++++++++++++++++++++++++ skills/README.md | 13 +++++++ skills/new-release-branch/SKILL.md | 3 +- skills/pre-release/SKILL.md | 16 +++----- 4 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 skills/README.md diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index cf1df6652..8f63bfdf4 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -48,6 +48,67 @@ guide. ### Memgraph v3.14.0 - October 21st, 2026 +{

⚠️ Breaking changes

} + +- `SHOW TRANSACTIONS` and `SHOW ACTIVE USERS INFO` each gain a `database` + column with the name of the database the session is using. `SHOW ACTIVE + USERS INFO` also gains `database marked for deletion`. Clients that parse + these results by column position need to account for the new fields. + [#4571](https://github.com/memgraph/memgraph/pull/4571) +- A pattern that names an already-bound variable is now a node test, including + in plain `MATCH`. After `OPTIONAL MATCH`, `MATCH (b)` no longer keeps a null + `b` row; it drops it (or errors if `b` is not a node). In `EXISTS` / `COUNT` / + `COLLECT`, a pattern may also no longer reuse a name from outside the body. + Rename those variables, or drop a no-op `MATCH` on a bound name. + [#4707](https://github.com/memgraph/memgraph/pull/4707) +- Comparing lists or maps that hold `null` now returns `null`, the same as + `null = null`, instead of treating them as equal. Update filters and + conditions that assumed `[null] = [null]` or `{a: null} = {a: null}` was + true. `DISTINCT` and grouping still treat those values as the same key. + [#4794](https://github.com/memgraph/memgraph/pull/4794) + +{

🐞 Bug fixes

} + +- `AFTER COMMIT` triggers no longer run for transactions that fail to commit + (for example a STRICT_SYNC write that aborts). They fire only when the + commit succeeds. On shutdown or `DROP TRIGGER`, any queued trigger work that + cannot run is logged instead of being discarded silently. + [#4565](https://github.com/memgraph/memgraph/pull/4565) +- Switching a database to on-disk storage mode is less likely to fail because of + brief concurrent activity (for example a metrics scrape or a Lab connection + between queries). Those no longer block the switch immediately. If another + session still holds the database after a short wait, the switch fails as + before, because exclusive access is required. + [#4569](https://github.com/memgraph/memgraph/pull/4569) +- Dropping or renaming a multi-tenant database with a tenant profile now + persists correctly, including across crashes and replication. + [#4570](https://github.com/memgraph/memgraph/pull/4570) +- Dropping an index is safe to roll back and to replay after recovery, including + when drops run alongside other writers or each other. + [#4741](https://github.com/memgraph/memgraph/pull/4741) +- Bolt stays usable when a query fails after streaming a large result. + [#4758](https://github.com/memgraph/memgraph/pull/4758) +- Creating or resuming a database at the memory limit no longer fails while + setting up stream support. + [#4785](https://github.com/memgraph/memgraph/pull/4785) + +{

🛠️ Improvements

} + +- Queries that set a timeout do less bookkeeping while they run, because the + limit is checked against a shared clock instead of a per-query timer. + [#4662](https://github.com/memgraph/memgraph/pull/4662) +- `KSHORTEST` path expansion is much faster and uses less memory, especially + for larger `k`, and equality and ordering comparisons between values are + faster. + [#4733](https://github.com/memgraph/memgraph/pull/4733) + [#4760](https://github.com/memgraph/memgraph/pull/4760) +- Commits skip uniqueness and existence constraint checks for writes that + cannot affect any constraint, so concurrent writers wait less on validation. + [#4769](https://github.com/memgraph/memgraph/pull/4769) +- Building Memgraph from source can reuse the compiler cache across different + checkout paths of the same repository. + [#4795](https://github.com/memgraph/memgraph/pull/4795) + ### Lab v3.14.0 - October 21st, 2026 diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 000000000..55410a9a1 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,13 @@ +# Documentation release skills + +Each skill lives in its own folder. Cursor requires the file to be named +`SKILL.md`, so use the **folder name** to tell them apart. + +| Skill (folder) | Purpose | +| --- | --- | +| [check-release-milestone](./check-release-milestone/SKILL.md) | Audit milestone docs labels, changelog coverage, and docs PRs before/during a release | +| [new-release-branch](./new-release-branch/SKILL.md) | Create a release branch, scaffold release-note titles, and open the tracking PR | +| [pre-release](./pre-release/SKILL.md) | Final pre-publish steps (release dates, direct download links) | +| [write-changelog-item](./write-changelog-item/SKILL.md) | Write one changelog / release-note bullet and sync the docs tracking PR | + +Open `.//SKILL.md` for the full instructions. diff --git a/skills/new-release-branch/SKILL.md b/skills/new-release-branch/SKILL.md index 78a76fa43..10acfeac1 100644 --- a/skills/new-release-branch/SKILL.md +++ b/skills/new-release-branch/SKILL.md @@ -121,7 +121,6 @@ Use this exact body template (fill in the placeholders): ~~~ Make sure to do: -* [ ] update sitemap * [ ] update direct download links Milestones @@ -172,4 +171,4 @@ gh api repos/memgraph/memgraph/milestones/ \ Print the PR URL and remind the user to: - Fill in the PR/author placeholders in the PR description. -- Update the sitemap and direct download links before merging. +- Update the direct download links before merging. diff --git a/skills/pre-release/SKILL.md b/skills/pre-release/SKILL.md index 1b0ecf596..64a2b7954 100644 --- a/skills/pre-release/SKILL.md +++ b/skills/pre-release/SKILL.md @@ -1,13 +1,13 @@ --- name: pre-release -description: Run final pre-release tasks for the documentation site. Updates release note dates, direct download links to the new Memgraph version, and rebuilds the site to regenerate the sitemap. Use when preparing a documentation release, before publishing, or when asked to run "pre-release" steps. +description: Run final pre-release tasks for the documentation site. Updates release note dates and direct download links to the new Memgraph version. Use when preparing a documentation release, before publishing, or when asked to run "pre-release" steps. --- # Pre-release Run these steps right before publishing a new documentation release to ensure -release dates are correct, download links point to the latest Memgraph version, -and the sitemap is up-to-date. +release dates are correct and download links point to the latest Memgraph +version. ## When to use @@ -44,11 +44,5 @@ replace **every** occurrence of the previous version with the new one. - Verify the result: every URL in the file should reference only the new version. -### 3. Rebuild the site to regenerate the sitemap - -Run `pnpm build` from the documentation root. The `postbuild` script -(`next-sitemap`) regenerates `sitemap.xml` automatically. - -Confirm the build finishes successfully and the sitemap generation summary -appears at the end of the output (look for `[next-sitemap] Generation -completed`). +Do **not** regenerate or commit `sitemap.xml` as part of a release. The sitemap +is no longer updated per release. From bda49f35d5db836046f862459be5e1a6ac088df9 Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Fri, 2 Oct 2026 15:31:30 +0200 Subject: [PATCH 05/13] docs: unlisted auth schemes pass through on coordinators without SSO (#1792) Document memgraph/memgraph#4962: on a coordinator, any Bolt scheme not listed in --auth-module-mappings now takes the same passthrough path as basic/none instead of being rejected outright. Describe the two-path rule, the per-scheme denial messages once SSO is configured, and the 3.13 -> 3.14 behavior change. --- .../coordinator-authentication.mdx | 97 ++++++++++++------- .../ha-commands-reference.mdx | 8 +- 2 files changed, 68 insertions(+), 37 deletions(-) diff --git a/pages/clustering/high-availability/coordinator-authentication.mdx b/pages/clustering/high-availability/coordinator-authentication.mdx index 6ec9b1223..195a8ee23 100644 --- a/pages/clustering/high-availability/coordinator-authentication.mdx +++ b/pages/clustering/high-availability/coordinator-authentication.mdx @@ -42,7 +42,8 @@ carrying a coordinator privilege mask. | 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)) | +| Basic auth | Username and password are validated | Passthrough — credentials are ignored (see [below](#authentication-passthrough)) | +| Other Bolt schemes | Rejected unless mapped to an auth module | Passthrough, same as basic auth (see [below](#authentication-passthrough)) | | SSO | Supported (OIDC, SAML, Kerberos) | Supported (OIDC, SAML, Kerberos) | Because roles live in the Raft log, they survive coordinator restarts, are @@ -110,57 +111,88 @@ 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: +A coordinator sorts every Bolt connection into one of two paths based on the +authentication scheme the client sends: -``` -The "" authentication scheme isn't supported on this coordinator; -connect with basic auth or an SSO scheme listed in the auth-module-mappings flag. -``` +- A scheme **listed in `--auth-module-mappings`** takes the [SSO + path](#sso-authentication): the coordinator runs the mapped auth module and + genuinely authenticates the connection. +- **Every other scheme** — `basic`, `none`, or a scheme that is **not** listed + in `--auth-module-mappings` — takes the [passthrough + path](#authentication-passthrough) described next. + +There is no separate "unsupported scheme" rule. A client that keeps its +data-instance SSO configuration (for example, `oidc`) when talking to a +coordinator that has no SSO configured is treated exactly like a basic-auth +client. -### Basic authentication passthrough +### 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 +When SSO is not in effect, connecting with a username and password, with no +auth at all, or with any scheme not listed in `--auth-module-mappings` +**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: + +**Memgraph 3.13 rejected unlisted schemes.** In 3.13, a coordinator only let +`basic`/`none` through and refused any other scheme outright, so drivers that +sent their data-instance SSO scheme to a coordinator without SSO stopped +connecting after an upgrade from 3.12. From Memgraph 3.14, an unlisted scheme +follows the same passthrough rule as `basic`/`none`, restoring the 3.12 +behavior. + + +Passthrough 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: +In that case the connection is rejected. The message tells `basic`/`none` apart +from an unlisted scheme, because the remedy differs: the former needs an SSO +login, while the latter is usually a misspelled or unconfigured scheme. + +For `basic`/`none`: ``` Basic authentication is disabled on this coordinator because SSO is configured; connect with an SSO scheme listed in the auth-module-mappings flag. ``` +For any other scheme not listed in `--auth-module-mappings`: + +``` +The "" authentication scheme isn't supported on this coordinator; +connect with an SSO scheme listed in the auth-module-mappings flag. +``` + +The deny rule applies to arbitrary scheme names as well as to `basic`, so a +client cannot bypass the SSO privilege model by sending a made-up scheme. + **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.** +privileged session to anybody — so the passthrough stays open, and you use a +basic-auth connection to create the first role and grant it `COORDINATOR_WRITE`. +The moment that grant commits to Raft, the passthrough 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. +`COORDINATOR_WRITE` role on a live cluster, the passthrough reopens rather than +leaving the cluster unadministrable. Unlisted schemes are admitted during +break-glass in the same way as `basic`/`none`. **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: +or invalid, SSO rejects every login. Condition 2 above means `basic`/`none` and +unlisted schemes fall 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'; @@ -174,7 +206,7 @@ refresh. ### A follower without quorum cannot be logged into -Both the basic-auth decision and the SSO role check need the **leader's** +Both the passthrough 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 @@ -191,14 +223,14 @@ license: 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. +- **Passthrough logins (`basic`/`none` and unlisted schemes) 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. +passthrough never contacts the leader, so it keeps working on a partitioned +follower. ### SSO authentication @@ -281,11 +313,10 @@ 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`. + 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`. + already removed. A passthrough session has no roles and returns `null`. ### Auth queries rejected on coordinators diff --git a/pages/clustering/high-availability/ha-commands-reference.mdx b/pages/clustering/high-availability/ha-commands-reference.mdx index b574a69ba..ea1a25587 100644 --- a/pages/clustering/high-availability/ha-commands-reference.mdx +++ b/pages/clustering/high-availability/ha-commands-reference.mdx @@ -910,11 +910,11 @@ SHOW CURRENT ROLE; - **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`. + session-local and works even when the leader is unreachable. A passthrough + session (basic auth or any scheme not mapped to an SSO module) 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`. + committed role set, so a dropped role stops being reported. A passthrough + session has no roles and returns `null`. ## Error handling From 4930234600891b0c823843fbe5dca0a3a0089db3 Mon Sep 17 00:00:00 2001 From: Andi Skrgat Date: Fri, 2 Oct 2026 15:32:11 +0200 Subject: [PATCH 06/13] docs: document disk_available row in SHOW STORAGE INFO (#1791) Documents the new instance-level disk_available row added in memgraph/memgraph#4952. Adds it to the SHOW STORAGE INFO reference table and the example output. Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- pages/database-management/server-stats.mdx | 3 ++- pages/fundamentals/storage-memory-usage.mdx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pages/database-management/server-stats.mdx b/pages/database-management/server-stats.mdx index c3ffc364f..158cd2a07 100644 --- a/pages/database-management/server-stats.mdx +++ b/pages/database-management/server-stats.mdx @@ -81,6 +81,7 @@ process (across all databases): | memory_res | The non-swapped physical RAM memory (RSS) used by the whole Memgraph process, as reported by the OS (in B, KiB, MiB, GiB or TiB). | | peak_memory_res | Peak RAM memory (RSS) usage of the Memgraph process during the whole run. | | disk_usage | The amount of disk space used by the data directory ([`--data-directory`](/database-management/configuration), in B, KiB, MiB, GiB or TiB). | +| disk_available | The amount of disk space still available on the filesystem that holds the data directory ([`--data-directory`](/database-management/configuration), in B, KiB, MiB, GiB or TiB). Reads `"unknown"` if the filesystem cannot be queried. | | memory_tracked | The total amount of RAM allocated across the instance and tracked by Memgraph (in B, KiB, MiB, GiB or TiB).
For more info, check out [memory control](/fundamentals/storage-memory-usage). | | memory_limit | The active instance-level runtime memory limit, taken from the [`--memory-limit`](/database-management/configuration) flag (in B, KiB, MiB, GiB or TiB). | | license_memory_limit | The memory limit imposed by the active license, or `"unlimited"` if no license is active. With an `enterprise` license this gates total tracked memory; with an `ai_platform` license it gates only graph and query memory. See [License types](/database-management/enabling-memgraph-enterprise#license-types). | @@ -118,7 +119,7 @@ only exist for a running database — `query_memory_tracked`, `tenant_memory_limit` reads `unlimited`. Process-wide fields such as `vm_max_map_count`, `memory_res`, `peak_memory_res`, -`memory_tracked` and the license/runtime memory limits are **not** included — +`disk_available`, `memory_tracked` and the license/runtime memory limits are **not** included — those are only available from the unscoped `SHOW STORAGE INFO`. | Field | Description | diff --git a/pages/fundamentals/storage-memory-usage.mdx b/pages/fundamentals/storage-memory-usage.mdx index cf0c4b0c4..a7bae1182 100644 --- a/pages/fundamentals/storage-memory-usage.mdx +++ b/pages/fundamentals/storage-memory-usage.mdx @@ -806,6 +806,7 @@ SHOW STORAGE INFO; | "memory_res" | "43.16MiB" | | "peak_memory_res" | "43.16MiB" | | "disk_usage" | "104.46KiB" | +| "disk_available" | "212.37GiB" | | "memory_tracked" | "8.52MiB" | | "memory_limit" | "58.55GiB" | | "license_memory_limit" | "unlimited" | From 21588c6b1ea195dd351bc3bf44ade7a61730f808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:33:52 +0200 Subject: [PATCH 07/13] docs: allow UNWIND in an EXISTS, COUNT or COLLECT body (#1783) --- pages/querying/subquery-expressions.mdx | 32 +++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pages/querying/subquery-expressions.mdx b/pages/querying/subquery-expressions.mdx index 0ee56f896..06a15eb41 100644 --- a/pages/querying/subquery-expressions.mdx +++ b/pages/querying/subquery-expressions.mdx @@ -353,8 +353,9 @@ Output: ### Supported clauses A body is a read-only query — it never writes to the graph. It matches with -`MATCH`, filters with `WHERE`, aggregates through `WITH`, and may end in a -`RETURN`; it may also be a [`UNION`](#union) of such branches. +`MATCH`, iterates a list with `UNWIND`, filters with `WHERE`, aggregates +through `WITH`, and may end in a `RETURN`; it may also be a [`UNION`](#union) +of such branches. `WITH` lets the body aggregate and then filter on the aggregate — here, people who acted in at least two movies: @@ -399,6 +400,33 @@ Output: +---------+ ``` +`UNWIND` turns a list into rows inside the body — here, how many of the given +years each person has a movie in: + +```cypher +MATCH (p:Person) +RETURN p.name AS name, + COUNT { + UNWIND [1995, 1999, 2003] AS year + MATCH (p)-[:ACTED_IN]->(:Movie {released: year}) + RETURN DISTINCT year + } AS years +ORDER BY name; +``` + +Output: + +```nocopy ++---------+-------+ +| name | years | ++---------+-------+ +| "Alice" | 2 | +| "Bob" | 1 | +| "Carol" | 0 | +| "Dave" | 0 | ++---------+-------+ +``` + ### The bare pattern shorthand `EXISTS` and `COUNT` accept a bare pattern in place of a full body, which is a From 4558990263bcd63cbae68152becf2dc2aa363fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:34:29 +0200 Subject: [PATCH 08/13] docs: add collections.index_of (#1782) --- .../available-algorithms.mdx | 1 + .../available-algorithms/collections.mdx | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pages/advanced-algorithms/available-algorithms.mdx b/pages/advanced-algorithms/available-algorithms.mdx index 5ea3aeb3a..097b79fa8 100644 --- a/pages/advanced-algorithms/available-algorithms.mdx +++ b/pages/advanced-algorithms/available-algorithms.mdx @@ -168,6 +168,7 @@ Running `SHOW QUERY CALLABLE MAPPINGS` requires the `CONFIG` privilege. | apoc.coll.unionAll | Returns the union of two input lists, including duplicates | [collections.union_all()](/advanced-algorithms/available-algorithms/collections#union_all) | | apoc.coll.removeAll | Removes defined elements from the input list | [collections.remove_all()](/advanced-algorithms/available-algorithms/collections#remove_all) | | apoc.coll.contains | Verifies the existence of an input value in an input list | [collections.contains()](/advanced-algorithms/available-algorithms/collections#contains) | +| apoc.coll.indexOf | Returns the position of the first element equal to a value | [collections.index_of()](/advanced-algorithms/available-algorithms/collections#index_of) | | apoc.coll.flatten | Returns flattened list of inputs provided | [collections.flatten()](/advanced-algorithms/available-algorithms/collections#flatten) | | apoc.coll.frequenciesAsMap | Returns a map of frequencies of the items in the collection | [collections.frequencies_as_map()](/advanced-algorithms/available-algorithms/collections#frequencies_as_map) | | apoc.coll.pairs | Creates pairs of neighbor elements within an input list | [collections.pairs()](/advanced-algorithms/available-algorithms/collections#pairs) | diff --git a/pages/advanced-algorithms/available-algorithms/collections.mdx b/pages/advanced-algorithms/available-algorithms/collections.mdx index bf80aa9d0..ac07a1895 100644 --- a/pages/advanced-algorithms/available-algorithms/collections.mdx +++ b/pages/advanced-algorithms/available-algorithms/collections.mdx @@ -46,7 +46,7 @@ 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` | +| `sum()`, `sum_longs()`, `avg()`, `min()`, `max()`, `to_set()`, `pairs()`, `index_of()` | `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 `[]` | @@ -58,6 +58,7 @@ argument-validation error. Each function returns a defined result: - `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`. +- `index_of()` returns `null` for a `NULL` search value, and `-1` for a search value that holds a `NULL` at any depth (`NULL = x` is never true). A `NULL` list element matches nothing. - `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. @@ -281,6 +282,41 @@ RETURN collections.contains([1,2,3], "e") AS output; +---------------------------------------------------------+ ``` +### `index_of()` + +Returns the position of the first element in a list that is equal to an input +value, compared with `=`. + + +This function is equivalent to Cypher's **coll.indexOf**. + + +{

Input:

} + +- `coll: List[Any]` ➡ The input list searched for the value. +- `value: Any` ➡ The input value searched in the list. + +{

Output:

} + +- `integer` ➡ The zero-based position of the first element equal to `value`, + or `-1` if no element is. + +{

Usage:

} + +The following query will return the position of the first `5` in the list: + +```cypher +RETURN collections.index_of([1, 5, 3, 5], 5) AS index; +``` + +```plaintext ++---------------------------------------------------------+ +| index | ++---------------------------------------------------------+ +| 1 | ++---------------------------------------------------------+ +``` + ### `contains_all()` Checks if a list contains all the values from another list. From b6ac32133ed2c933aa6407f18c140559777c6064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:38:50 +0200 Subject: [PATCH 09/13] feat: label expressions in node patterns and expressions (#1797) * docs: label expressions in node patterns and expressions * Update pages/querying/differences-in-cypher-implementations.mdx --------- Co-authored-by: Vlasta <95473291+vpavicic@users.noreply.github.com> --- pages/querying/clauses/match.mdx | 161 +++++++++++++++++- .../differences-in-cypher-implementations.mdx | 5 +- 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/pages/querying/clauses/match.mdx b/pages/querying/clauses/match.mdx index 2cf2f58f5..eee8ba9de 100644 --- a/pages/querying/clauses/match.mdx +++ b/pages/querying/clauses/match.mdx @@ -14,6 +14,10 @@ The `MATCH` clause is used to obtain data from the database by matching it to a 1.2. [Get all nodes with a label](#12-get-all-nodes-with-a-label)
1.3. [Get nodes with with all of multiple labels](#13-get-nodes-with-all-labels)
1.4. [Get nodes with any of multiple labels](#14-get-nodes-with-any-labels)
+ 1.5. [Get nodes without a label](#15-get-nodes-without-a-label)
+ 1.6. [Get nodes with any label](#16-get-nodes-with-any-label)
+ 1.7. [Combine label expressions](#17-combine-label-expressions)
+ 1.8. [Label expressions in expressions](#18-label-expressions-in-expressions)
2. [Matching relationships](#2-matching-relationships)
2.1. [Get all related nodes](#21-get-all-related-nodes)
2.2. [Get related nodes with a label](#22-get-related-nodes-with-a-label)
@@ -106,6 +110,13 @@ MATCH (n:Person:Employee) RETURN n; ``` +The `&` (AND) operator gives the same result: + +```cypher +MATCH (n:Person&Employee) +RETURN n; +``` + Output: ```nocopy +-----------------------------------+ @@ -143,7 +154,155 @@ This allows you to retrieve nodes of different types in a single query, making l -Label OR expressions (`|`) can't be used in `MERGE` or `CREATE` clauses. +`CREATE` and `MERGE` accept only a conjunction of labels, such as `(n:Person:Employee)` or +`(n:Person&Employee)`. A label expression with `|`, `!` or `%` raises an error there, because it does +not name the labels a new node gets. + + + +### 1.5. Get nodes without a label + +Use the `!` (NOT) operator to match nodes that do **not** have a label: + +```cypher +MATCH (n:!Person) +RETURN n; +``` + +Output: +```nocopy ++-----------------------------------------------------------------------------------------------------+ +| n | ++-----------------------------------------------------------------------------------------------------+ +| (:Country {continent: "Europe", language: "German", name: "Germany", population: 83000000}) | +| (:Country {continent: "Europe", language: "French", name: "France", population: 67000000}) | +| (:Country {continent: "Europe", language: "English", name: "United Kingdom", population: 66000000}) | +| (:City {name: "Berlin", population: 3600000}) | +| (:City {name: "Paris", population: 2140000}) | ++-----------------------------------------------------------------------------------------------------+ +``` + +### 1.6. Get nodes with any label + +The `%` (wildcard) matches a node that has at least one label. `!%` matches a node that has no labels. + +```cypher +MATCH (n:%) +RETURN count(n) AS labelled; +``` + +Output: +```nocopy ++----------+ +| labelled | ++----------+ +| 8 | ++----------+ +``` + +### 1.7. Combine label expressions + +The operators `&`, `|`, `!` and `%` combine into one label expression. `!` binds tightest, then `&`, then +`|`. Use parentheses to change the order. + +```cypher +MATCH (n:Person&!Employee) +RETURN n; +``` + +Output: +```nocopy ++---------------------------+ +| n | ++---------------------------+ +| (:Person {name: "Harry"}) | +| (:Person {name: "Anna"}) | ++---------------------------+ +``` + +`City|Person&Employee` reads as `City|(Person&Employee)`: + +```cypher +MATCH (n:City|Person&Employee) +RETURN n.name AS name ORDER BY name; +``` + +Output: +```nocopy ++----------+ +| name | ++----------+ +| "Berlin" | +| "John" | +| "Paris" | ++----------+ +``` + +With parentheses, the same labels give a different result: + +```cypher +MATCH (n:(City|Person)&Employee) +RETURN n.name AS name ORDER BY name; +``` + +Output: +```nocopy ++--------+ +| name | ++--------+ +| "John" | ++--------+ +``` + +The colon form `:A:B` can't be mixed with the operators in one expression: `(n:Person:Employee&!City)` +raises a syntax error. Write `(n:Person&Employee&!City)` instead. + +### 1.8. Label expressions in expressions + +A label expression can test a node anywhere an expression is allowed, such as `WHERE`, `RETURN`, `WITH` +and `CASE`. It returns `true` or `false`, and `null` for a `null` value. + +```cypher +MATCH (n) +WHERE n:City|Country +RETURN n.name AS name ORDER BY name; +``` + +Output: +```nocopy ++------------------+ +| name | ++------------------+ +| "Berlin" | +| "France" | +| "Germany" | +| "Paris" | +| "United Kingdom" | ++------------------+ +``` + +```cypher +MATCH (p:Person) +RETURN p.name AS name, p:Employee AS employee, p:!Employee AS not_employee ORDER BY name; +``` + +Output: +```nocopy ++--------------+--------------+--------------+ +| name | employee | not_employee | ++--------------+--------------+--------------+ +| "Anna" | false | true | +| "Harry" | false | true | +| "John" | true | false | ++--------------+--------------+--------------+ +``` + +A label expression binds tighter than every other operator, so `n:A&B = true` tests `(n:A&B) = true`. + + + +In a list comprehension, the last `|` is the projection. `[x IN list WHERE x:A|B]` filters on `x:A` and +projects `B`. Write `[x IN list WHERE x:(A|B)]` to filter on the disjunction. diff --git a/pages/querying/differences-in-cypher-implementations.mdx b/pages/querying/differences-in-cypher-implementations.mdx index d51420639..8964efdac 100644 --- a/pages/querying/differences-in-cypher-implementations.mdx +++ b/pages/querying/differences-in-cypher-implementations.mdx @@ -134,8 +134,9 @@ RETURN p; Note that Memgraph requires both source and target nodes to be matched first using a `WITH` clause before applying the K-shortest paths algorithm. -### NOT label expression -In Neo4j, you can use the `NOT` label expression (`!`): +### Relationship type expressions +Memgraph supports [label expressions](/querying/clauses/match#17-combine-label-expressions) on nodes +(`&`, `|`, `!`, `%` and parentheses). On relationship types, it supports only `|`. Memgraph doesn't support the use of `!` on a relationship type: ```cypher MATCH (:Person {name:'Tom Hanks'})-[r:!ACTED_IN]->(m:Movie) Return type(r) AS type, m.title AS movies From 1f7dfebb5a2dfea5646f9ba6b1ce3a59c2e12762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Milinovi=C4=87?= <44698587+imilinovic@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:41:40 +0200 Subject: [PATCH 10/13] Document OPTIONAL CALL subqueries (#1776) `OPTIONAL CALL ( scope ) { body }` keeps an input row whose subquery returns no rows, with the subquery's columns set to null - the OPTIONAL MATCH contract. Section 2.4 said the scoped `CALL (...)` form could not be combined with `OPTIONAL` and showed the exact query that now works. It is replaced by 1.7 under the supported uses, and 2.4 now covers the case that is still rejected: `OPTIONAL` on a procedure call rather than on a subquery, with the subquery wrapping that does work. --- pages/querying/clauses/call.mdx | 73 +++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/pages/querying/clauses/call.mdx b/pages/querying/clauses/call.mdx index 545173d9c..4d456435b 100644 --- a/pages/querying/clauses/call.mdx +++ b/pages/querying/clauses/call.mdx @@ -22,13 +22,14 @@ Switch to MAGE documentation if you want to CALL a graph algorithm or some other 1.3. [Post-union processing](#13-post-union-processing)
1.4. [Observing changes from previous executions](#14-observing-changes-from-previous-executions)
1.5. [Unit subqueries](#15-unit-subqueries)
- 1.6. [Scoped variables with `CALL (...)`](#16-scoped-variables-with-call-) + 1.6. [Scoped variables with `CALL (...)`](#16-scoped-variables-with-call-)
+ 1.7. [Optional subqueries with `OPTIONAL CALL`](#17-optional-subqueries-with-optional-call) 2. [Invalid uses of CALL subquery](#2-invalid-uses-of-call-subquery)
2.1. [Returning variables with the same name as those in the outer scope](#21-returning-variables-with-same-name-as-those-in-the-outer-scope)
2.2. [Returning non-aliased expressions](#22-returning-non-aliased-expressions)
2.3. [Referencing outer scope variables that don't exist](#22-referencing-outer-scope-variables-that-dont-exist)
- 2.4. [Using `OPTIONAL CALL` with scoped variables](#24-using-optional-call-with-scoped-variables)
+ 2.4. [Using `OPTIONAL` with a procedure call](#24-using-optional-with-a-procedure-call)
## 1. Uses of CALL subquery @@ -224,6 +225,48 @@ The scoped form is equivalent to using `WITH` to import variables, but narrows the subquery's scope explicitly at the boundary, which makes the import list visible at a glance. +### 1.7. Optional subqueries with `OPTIONAL CALL` + +`CALL` drops an input row when the subquery returns no rows for it. `OPTIONAL +CALL` keeps that row and sets the columns the subquery returns to `null`, which +is the same contract as [`OPTIONAL MATCH`](/querying/clauses/optional-match). + +Imagine the data from [1.2](#12-cartesian-products-with-bounded-symbols), plus a +third `:Person` named `Carol` who has no `:HAS_PARENT` relationship: + +```cypher +MATCH (person:Person) +OPTIONAL CALL (person) { + MATCH (person)-[:HAS_PARENT]->(parent:Parent) + RETURN parent.name AS parent_name +} +RETURN person.name AS person_name, parent_name +``` + +Output: +```nocopy ++-------------+-------------+ +| person_name | parent_name | ++---------------------------+ +| 'John' | 'John Sr.' | +| 'John' | 'Anna' | +| 'Alice' | 'Roxanne' | +| 'Alice' | 'Bill' | +| 'Carol' | null | ++---------------------------+ +``` + +Without `OPTIONAL`, `Carol` does not appear in the result at all. + +`OPTIONAL` can be combined with every scope form (`CALL (v1, v2)`, `CALL (*)`, +`CALL ()`, or no scope clause), with a `UNION` subquery, with nested subqueries, +and with [`IN TRANSACTIONS`](/querying/read-and-modify-data#call-subqueries-in-transactions). + +Variables imported from the outer scope keep their values; only the columns the +subquery itself introduces are set to `null`. A +[unit subquery](#15-unit-subqueries) returns no columns, so `OPTIONAL` has +nothing to set to `null` and the number of rows is the same either way. + ## 2. Invalid uses of CALL subquery ### 2.1. Returning variables with the same name as those in the outer scope @@ -308,19 +351,25 @@ CALL { RETURN DISTINCT n; ``` -### 2.4. Using `OPTIONAL CALL` with scoped variables +### 2.4. Using `OPTIONAL` with a procedure call -The scoped `CALL (...)` form cannot be combined with `OPTIONAL`: +`OPTIONAL` applies to a `CALL` subquery, not to a procedure call: ```cypher -MATCH (p:Player) +MATCH (p:Person) +OPTIONAL CALL algo.procedure(p) +YIELD result +RETURN p.name, result; +``` + +The above query results in an error. Wrap the procedure call in a subquery +instead: + +```cypher +MATCH (p:Person) OPTIONAL CALL (p) { - MATCH (p)-[:PLAYS_FOR]->(team:Team) - RETURN team.name AS team + CALL algo.procedure(p) YIELD result + RETURN result } -RETURN p.name, team; +RETURN p.name, result; ``` - -The above query results in an error. To make a scoped subquery optional, -emit a sentinel value from the inner block instead, or fall back to the -plain `OPTIONAL MATCH` outside the `CALL`. From cf0083d79d7aebdfc5993727b41a4d87264c6084 Mon Sep 17 00:00:00 2001 From: Vlasta Date: Fri, 2 Oct 2026 16:16:52 +0200 Subject: [PATCH 11/13] Complete the Memgraph v3.14.0 changelog. Fill in the remaining breaking, feature, fix, and improvement notes for merged memgraph PRs on this release. Co-authored-by: Cursor --- pages/release-notes.mdx | 187 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 8f63bfdf4..e4121a3d9 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -55,25 +55,113 @@ guide. USERS INFO` also gains `database marked for deletion`. Clients that parse these results by column position need to account for the new fields. [#4571](https://github.com/memgraph/memgraph/pull/4571) +- Edge index operator metrics are merged into + `memgraph_scan_all_by_edge_type_property_operator_total` and + `memgraph_scan_all_by_edge_property_operator_total`. Dashboards that + scrape the older `_value` or `_range` series need those names updated. + [#4671](https://github.com/memgraph/memgraph/pull/4671) +- Comparing an edge property with a type it cannot match, such as a + boolean or a point, now returns no rows instead of raising. Update + queries that relied on that error. + [#4671](https://github.com/memgraph/memgraph/pull/4671) - A pattern that names an already-bound variable is now a node test, including in plain `MATCH`. After `OPTIONAL MATCH`, `MATCH (b)` no longer keeps a null `b` row; it drops it (or errors if `b` is not a node). In `EXISTS` / `COUNT` / `COLLECT`, a pattern may also no longer reuse a name from outside the body. Rename those variables, or drop a no-op `MATCH` on a bound name. [#4707](https://github.com/memgraph/memgraph/pull/4707) +- Functions in the `collections` query module (`contains`, `to_set`, `union` + and `duplicates`) now treat maps with the same keys and values as equal, + matching Cypher's `=`. Queries that relied on every non-empty map being + distinct will see different results. Custom C++ query modules need to be + recompiled for this release. + [#4783](https://github.com/memgraph/memgraph/pull/4783) - Comparing lists or maps that hold `null` now returns `null`, the same as `null = null`, instead of treating them as equal. Update filters and conditions that assumed `[null] = [null]` or `{a: null} = {a: null}` was true. `DISTINCT` and grouping still treat those values as the same key. [#4794](https://github.com/memgraph/memgraph/pull/4794) +- `NaN > x` and `NaN >= x` now return `false` instead of `true`. A filter + on those operators keeps no row where it previously kept every row. + [#4816](https://github.com/memgraph/memgraph/pull/4816) +- Comparing values with `<`, `<=`, `>` or `>=` no longer fails the query + when the pair has no order. Two booleans are ordered as Cypher orders + them (`false` < `true`). A pair such as two points or two lists + returns `null`. + [#4816](https://github.com/memgraph/memgraph/pull/4816) +- An indexed range over a date, local time, local date time or duration + returns only values of that same kind. It previously also returned the + other three. Recheck range queries on mixed temporal properties. + [#4816](https://github.com/memgraph/memgraph/pull/4816) +- A uniqueness constraint now allows two nodes whose property holds + `null` at any depth, such as `[1, null]` or `{a: null}`. It + previously refused the second. Recheck constraints that relied on + that refusal. + [#4836](https://github.com/memgraph/memgraph/pull/4836) + +{

✨ New features

} + +- `DROP DATABASE ... FORCE` no longer blocks other databases on the instance + while the tenant is torn down. A database still draining is shown as + `DROPPING` in `SHOW DATABASES`. If the same name is created again while + the old tenant is draining, that row is shown as `name (uuid)`. Plain + `DROP DATABASE` is unchanged. + [#4573](https://github.com/memgraph/memgraph/pull/4573) +- `SHOW SESSIONS` lists active Bolt sessions (`session_id`, `username`, + `database`, `login_timestamp`). `TERMINATE SESSIONS` evicts sessions by + uuid from another connection. A user may manage their own sessions; + terminating another user's session requires `TRANSACTION_MANAGEMENT` on + that session's database. `SESSIONS` is a reserved keyword; quote it as a + bare identifier. + [#4577](https://github.com/memgraph/memgraph/pull/4577) +- The `collections` query module adds `index_of(coll, value)`: the position + of the first element equal to `value`, or `-1`. A null collection or + value returns null. + [#4783](https://github.com/memgraph/memgraph/pull/4783) +- The built-in `nullIf(value1, value2)` returns null when the two arguments + are equal, and the first argument otherwise — the counterpart of + `coalesce`. The comparison is the same as `=`, so `nullIf(1, null)` + returns `1`. + [#4786](https://github.com/memgraph/memgraph/pull/4786) +- `OPTIONAL CALL` keeps an input row when the subquery body returns + nothing for it, and sets the body's returned columns to null. Without + `OPTIONAL`, that row is dropped. Supported with a scope clause, + `(*)`, no scope, `UNION`, nesting, and `IN TRANSACTIONS`. + [#4833](https://github.com/memgraph/memgraph/pull/4833) +- Aggregation functions can be used inside a `CASE` expression in + `RETURN` and `WITH`. They were previously refused. Aggregations in + `ORDER BY` are still refused. + [#4859](https://github.com/memgraph/memgraph/pull/4859) +- The FIPS Docker image now includes a Python interpreter, so Python + query modules and auth modules run on that image. + [#4902](https://github.com/memgraph/memgraph/pull/4902) +- An `EXISTS { ... }`, `COUNT { ... }` or `COLLECT { ... }` body can + contain `UNWIND`, for example `COUNT { UNWIND n.list AS x RETURN x }`. + Write clauses stay refused in these bodies. + [#4915](https://github.com/memgraph/memgraph/pull/4915) +- `SHOW STORAGE INFO` now reports `disk_available`, the free space on + the filesystem that holds the data directory. + [#4952](https://github.com/memgraph/memgraph/pull/4952) {

🐞 Bug fixes

} +- After a data instance joins a high-availability cluster, OpenMetrics + scrapes now use the cluster UUID for the default database on every + instance. Series for `database="memgraph"` can be aggregated across + the cluster. + [#4275](https://github.com/memgraph/memgraph/pull/4275) - `AFTER COMMIT` triggers no longer run for transactions that fail to commit (for example a STRICT_SYNC write that aborts). They fire only when the commit succeeds. On shutdown or `DROP TRIGGER`, any queued trigger work that cannot run is logged instead of being discarded silently. [#4565](https://github.com/memgraph/memgraph/pull/4565) +- During a license refresh, a query could briefly skip Enterprise permission + checks. Those checks stay enforced while the license is revalidated. + [#4566](https://github.com/memgraph/memgraph/pull/4566) +- Dropping a database while a write is still committing no longer crashes a + replica registered with STRICT_SYNC. The in-flight commit is aborted + before the database is removed. + [#4568](https://github.com/memgraph/memgraph/pull/4568) - Switching a database to on-disk storage mode is less likely to fail because of brief concurrent activity (for example a metrics scrape or a Lab connection between queries). Those no longer block the switch immediately. If another @@ -83,20 +171,99 @@ guide. - Dropping or renaming a multi-tenant database with a tenant profile now persists correctly, including across crashes and replication. [#4570](https://github.com/memgraph/memgraph/pull/4570) +- `DROP DATABASE` no longer hangs when an index is being built or TTL is + running in the background. + [#4572](https://github.com/memgraph/memgraph/pull/4572) - Dropping an index is safe to roll back and to replay after recovery, including when drops run alongside other writers or each other. [#4741](https://github.com/memgraph/memgraph/pull/4741) - Bolt stays usable when a query fails after streaming a large result. [#4758](https://github.com/memgraph/memgraph/pull/4758) +- Dropping a database now also drops that database's server-side + parameters (`SET PARAMETER`), including across replication and + recovery. They no longer linger and reappear on replicas. + [#4774](https://github.com/memgraph/memgraph/pull/4774) - Creating or resuming a database at the memory limit no longer fails while setting up stream support. [#4785](https://github.com/memgraph/memgraph/pull/4785) +- A procedure inside `CALL { ... }`, and clauses below it in that + subquery, now run for every input row instead of only the first. + Affected queries return different results and can write different + data, including `CALL { ... } IN TRANSACTIONS`. + [#4832](https://github.com/memgraph/memgraph/pull/4832) +- A property holding `NaN` is kept in an index and found again. An + `IS NOT NULL` count on that property no longer shrinks or changes + from run to run once the index exists. + [#4836](https://github.com/memgraph/memgraph/pull/4836) +- `DISTINCT` and grouping treat two NaNs as one value. A point with a + NaN coordinate is handled the same way. `=` is unchanged and still + returns false for a NaN. + [#4846](https://github.com/memgraph/memgraph/pull/4846) +- `ORDER BY` places a NaN after every number, including positive + infinity, and two NaNs sort together. A column that held a NaN no + longer has an arbitrary order. + [#4846](https://github.com/memgraph/memgraph/pull/4846) +- A node with more than one unique constraint no longer leaks memory + when a write touches only one of them, including under + `USING PERIODIC COMMIT` and `CALL { ... } IN TRANSACTIONS`. + [#4848](https://github.com/memgraph/memgraph/pull/4848) +- `EXISTS { ... }`, `COUNT { ... }` and `COLLECT { ... }` in a `WHERE` + no longer fail or keep the wrong rows when the body reads the outer + query only through a `WHERE`, a `WITH`, a later `UNION` branch, or a + nested subquery. `WHERE n.prop = COUNT { ... }` also returns the same + rows with a label-property index as without one. + [#4874](https://github.com/memgraph/memgraph/pull/4874) +- `SHOW REPLICATION LAG` now reports the correct lag for `STRICT_SYNC` + replicas. + [#4884](https://github.com/memgraph/memgraph/pull/4884) +- `STARTS WITH` on an indexed property no longer returns nothing when + the prefix is made entirely of `0xFF` bytes. + [#4886](https://github.com/memgraph/memgraph/pull/4886) +- Creating or dropping a database no longer makes a healthy MAIN look + down to the coordinator. Cluster health checks are answered while + that operation holds its lock, so MAIN is not demoted. + [#4903](https://github.com/memgraph/memgraph/pull/4903) +- `SHOW INSTANCE` now works on a coordinator that is still joining + the cluster. + [#4911](https://github.com/memgraph/memgraph/pull/4911) +- Switching users on a pooled Bolt connection now clears the previous + user's `SET SESSION` settings. + [#4932](https://github.com/memgraph/memgraph/pull/4932) +- Setting a property to the value it already holds no longer fires an + `ON UPDATE` trigger when that value is a NaN, or a list or map that + holds a null. + [#4936](https://github.com/memgraph/memgraph/pull/4936) +- Label disjunctions now return the correct rows, including two + `:A|B` tests on the same node and `WHERE n:A OR m:B`. + [#4948](https://github.com/memgraph/memgraph/pull/4948) +- `valueType()` now returns `VIRTUAL_GRAPH` for the graph that + `derive()` returns. + [#4954](https://github.com/memgraph/memgraph/pull/4954) +- Comparing two lists with `<`, `<=`, `>` or `>=` now uses dictionary + order instead of raising. + [#4956](https://github.com/memgraph/memgraph/pull/4956) +- On a coordinator without SSO, login now succeeds with a Bolt + scheme that is not listed in `--auth-module-mappings`. + [#4962](https://github.com/memgraph/memgraph/pull/4962) +- `REMOVE COORDINATOR` now finishes if the same query is retried after + a timeout, instead of failing with "no such id". + [#4968](https://github.com/memgraph/memgraph/pull/4968) +- After `DROP DATABASE ... FORCE`, a Bolt connection that was on that + database is now closed, so the driver reconnects instead of staying + stuck on "Database required". + [#4969](https://github.com/memgraph/memgraph/pull/4969) {

🛠️ Improvements

} - Queries that set a timeout do less bookkeeping while they run, because the limit is checked against a shared clock instead of a per-query timer. [#4662](https://github.com/memgraph/memgraph/pull/4662) +- Edge property indexes now answer `STARTS WITH` from the index alone. + `CONTAINS`, `ENDS WITH` and regex read only the string run of the index, + not every value of the property. + [#4671](https://github.com/memgraph/memgraph/pull/4671) +- Replicas apply incoming writes about twice as fast. + [#4714](https://github.com/memgraph/memgraph/pull/4714) - `KSHORTEST` path expansion is much faster and uses less memory, especially for larger `k`, and equality and ordering comparisons between values are faster. @@ -108,6 +275,26 @@ guide. - Building Memgraph from source can reuse the compiler cache across different checkout paths of the same repository. [#4795](https://github.com/memgraph/memgraph/pull/4795) +- A pattern comprehension that filters on a value from the outer query + can use an index, for example + `MATCH (m:Item) RETURN [ (n:Item {id: m.id})--(q) | q ]`. + [#4819](https://github.com/memgraph/memgraph/pull/4819) +- `CONTAINS`, `ENDS WITH` and regex on an indexed string under + `USING PARALLEL EXECUTION` no longer scan the whole index column. + [#4827](https://github.com/memgraph/memgraph/pull/4827) +- `path.expand_config` uses less time and memory on a breadth-first walk + with `uniqueness: NODE_PATH` or `RELATIONSHIP_PATH` when many paths + reach the same node. Walks that never revisit a node can be slower. + A memory-limit error now reports how many partial paths were held and + suggests a hop bound or `uniqueness: NODE_GLOBAL`. + [#4875](https://github.com/memgraph/memgraph/pull/4875) +- `CONTAINS`, `ENDS WITH` and regex on an indexed edge property are + tested while reading the index, so non-matching values are dropped + there rather than by a later filter. + [#4900](https://github.com/memgraph/memgraph/pull/4900) +- Cypher queries now parse faster the first time they run, especially + relationships with a property map and `exists()` patterns. + [#4963](https://github.com/memgraph/memgraph/pull/4963) ### Lab v3.14.0 - October 21st, 2026 From 935c180ccf329224b95390438a6fc3508577b204 Mon Sep 17 00:00:00 2001 From: Vlasta Date: Thu, 8 Oct 2026 12:07:27 +0200 Subject: [PATCH 12/13] Add v3.14.0 release notes for nine more PRs and tighten bug-fix skill wording. Co-authored-by: Cursor --- pages/release-notes.mdx | 31 ++++++++++++++++++++++++++++ skills/write-changelog-item/SKILL.md | 24 +++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index 353b9b63d..cf7b07d8f 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -128,10 +128,21 @@ guide. `OPTIONAL`, that row is dropped. Supported with a scope clause, `(*)`, no scope, `UNION`, nesting, and `IN TRANSACTIONS`. [#4833](https://github.com/memgraph/memgraph/pull/4833) +- After `DROP DATABASE ... FORCE`, idle Bolt sessions still using that + database are closed so the drop can finish draining. Busy sessions are + left alone until they become idle. Clients see a disconnect and can + reconnect. Plain `DROP DATABASE` is unchanged. + [#4843](https://github.com/memgraph/memgraph/pull/4843) - Aggregation functions can be used inside a `CASE` expression in `RETURN` and `WITH`. They were previously refused. Aggregations in `ORDER BY` are still refused. [#4859](https://github.com/memgraph/memgraph/pull/4859) +- Node patterns and expressions accept label expressions built with `&`, + `|`, `!`, `%` and parentheses, for example `MATCH (n:Person&Employee)` + or `WHERE n:A|B`. `%` means the node has at least one label. `CREATE` + and `MERGE` accept only `&` and parentheses. `SET` and `REMOVE` still + take labels as `:Label` only. + [#4877](https://github.com/memgraph/memgraph/pull/4877) - The FIPS Docker image now includes a Python interpreter, so Python query modules and auth modules run on that image. [#4902](https://github.com/memgraph/memgraph/pull/4902) @@ -142,6 +153,10 @@ guide. - `SHOW STORAGE INFO` now reports `disk_available`, the free space on the filesystem that holds the data directory. [#4952](https://github.com/memgraph/memgraph/pull/4952) +- A `CALL` subquery can choose which body to run per input row with + `WHEN … THEN …` branches and an optional `ELSE`, for example + `CALL (n) { WHEN n.ok THEN CREATE (:A) ELSE CREATE (:B) }`. + [#4984](https://github.com/memgraph/memgraph/pull/4984) {

🐞 Bug fixes

} @@ -213,6 +228,8 @@ guide. nested subquery. `WHERE n.prop = COUNT { ... }` also returns the same rows with a label-property index as without one. [#4874](https://github.com/memgraph/memgraph/pull/4874) +- Concurrent edge writes no longer crash during garbage collection. + [#4878](https://github.com/memgraph/memgraph/pull/4878) - `SHOW REPLICATION LAG` now reports the correct lag for `STRICT_SYNC` replicas. [#4884](https://github.com/memgraph/memgraph/pull/4884) @@ -242,6 +259,8 @@ guide. - Comparing two lists with `<`, `<=`, `>` or `>=` now uses dictionary order instead of raising. [#4956](https://github.com/memgraph/memgraph/pull/4956) +- Edge vector indexes recover correctly after a crash or snapshot load. + [#4959](https://github.com/memgraph/memgraph/pull/4959) - On a coordinator without SSO, login now succeeds with a Bolt scheme that is not listed in `--auth-module-mappings`. [#4962](https://github.com/memgraph/memgraph/pull/4962) @@ -252,6 +271,16 @@ guide. database is now closed, so the driver reconnects instead of staying stuck on "Database required". [#4969](https://github.com/memgraph/memgraph/pull/4969) +- Periodic snapshots on a replica record the correct epoch even when a + new MAIN's first commit arrives while the snapshot is being written. + [#4982](https://github.com/memgraph/memgraph/pull/4982) +- `CALL` subqueries with a `UNION` or `UNION ALL` body return the outer + rows, make writes in or before the `CALL` visible to later clauses, and + expose the body's columns to `RETURN *`, `WITH *` and `CALL (*)`. + [#4983](https://github.com/memgraph/memgraph/pull/4983) +- `memgraph_show_instances_total` counts only `SHOW INSTANCES` queries. + OpenMetrics scrapes no longer increment it. + [#5005](https://github.com/memgraph/memgraph/pull/5005) {

🛠️ Improvements

} @@ -295,6 +324,8 @@ guide. - Cypher queries now parse faster the first time they run, especially relationships with a property map and `exists()` patterns. [#4963](https://github.com/memgraph/memgraph/pull/4963) +- Queries that evaluate expressions, including filters, run faster. + [#5014](https://github.com/memgraph/memgraph/pull/5014) ### Lab v3.14.0 - October 21st, 2026 diff --git a/skills/write-changelog-item/SKILL.md b/skills/write-changelog-item/SKILL.md index 59c111438..9a143964a 100644 --- a/skills/write-changelog-item/SKILL.md +++ b/skills/write-changelog-item/SKILL.md @@ -43,6 +43,30 @@ Avoid: - Second-person or first-person address ("you get a warning", "you can now", "we fixed"). State the behavior without naming the reader. +### Bug fixes + +For bug-fix bullets, lead with **what works now** (the correct, expected +behavior). Do not narrate internal failure modes, races, or data structures +when those details were never a documented or intentional user-facing +surface. + +Prefer: + +- "Periodic snapshots on a replica record the correct epoch even when …" +- "Edge vector indexes recover correctly after a crash or snapshot load." + +Avoid: + +- Long "no longer …" chains that explain the old crash path, dangling + pointers, or which lock was missing. +- Contrasting before/after internals the operator could not observe or + configure. + +If the only user-visible effect was a crash, wrong result, or rejected +replica, a short statement of the correct outcome is enough. Add a brief +trigger or context only when it helps an operator recognize the situation +(for example "while a new MAIN's first commit arrives"). + The changelog item should be benefit focused (highlight the why). The changelog item should be in the markdown format, don't remove ticks and markdown links, don't use bold and italic, use - at the beginning of the item. From e2f6c97ed1fcfe392456cf6b9635e3db66090b63 Mon Sep 17 00:00:00 2001 From: Vlasta Date: Tue, 13 Oct 2026 16:44:00 +0200 Subject: [PATCH 13/13] Add v3.14.0 release notes for 18 more milestone PRs. Co-authored-by: Cursor --- pages/release-notes.mdx | 72 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/pages/release-notes.mdx b/pages/release-notes.mdx index cf7b07d8f..e38fda3c3 100644 --- a/pages/release-notes.mdx +++ b/pages/release-notes.mdx @@ -150,6 +150,10 @@ guide. contain `UNWIND`, for example `COUNT { UNWIND n.list AS x RETURN x }`. Write clauses stay refused in these bodies. [#4915](https://github.com/memgraph/memgraph/pull/4915) +- `ORDER BY`, and a `WITH`'s `WHERE`, can repeat an expression that an + aggregating `RETURN` or `WITH` projects, for example + `RETURN n.type, count(n.value) ORDER BY count(n.value) DESC`. + [#4951](https://github.com/memgraph/memgraph/pull/4951) - `SHOW STORAGE INFO` now reports `disk_available`, the free space on the filesystem that holds the data directory. [#4952](https://github.com/memgraph/memgraph/pull/4952) @@ -217,7 +221,39 @@ guide. - `ORDER BY` places a NaN after every number, including positive infinity, and two NaNs sort together. A column that held a NaN no longer has an arbitrary order. - [#4846](https://github.com/memgraph/memgraph/pull/4846) + [#4849](https://github.com/memgraph/memgraph/pull/4849) +- `ORDER BY` sorts a column that holds more than one type, placing each + row by its type (map, node, relationship, list, path, string, boolean, + number, then null). A pair of the same type with no order of its own, + such as two maps, is still refused. + [#4852](https://github.com/memgraph/memgraph/pull/4852) +- An integer and a double are compared by the value each holds, so a + large integer is no longer treated as equal to the nearest double that + cannot represent it. `=`, ordering, `ORDER BY`, `DISTINCT`, indexes and + unique constraints agree on such pairs. `1 = 1.0` is unchanged. + [#4854](https://github.com/memgraph/memgraph/pull/4854) +- List properties keep large integers as written (for example + `2147483648` no longer comes back as a different number), and range + filters on list properties return the matching rows. + [#4865](https://github.com/memgraph/memgraph/pull/4865) +- An index holds property values in the same order as `ORDER BY`, so a + query over a column with more than one type returns the same rows in + the same order with or without an index. Two date-time values at the + same instant in different time zones are ordered by offset before zone + name. + [#4880](https://github.com/memgraph/memgraph/pull/4880) +- `MIN` and `MAX` work on a column that holds more than one type, and + return the first and last values in `ORDER BY` order. A NaN counts as + the largest number. + [#4882](https://github.com/memgraph/memgraph/pull/4882) +- `DISTINCT` and grouping tell apart doubles that fall between the same + pair of integers (for example `2.25` and `2.5`). A whole double still + matches the integer it equals. + [#4891](https://github.com/memgraph/memgraph/pull/4891) +- Hash lookups find a list of numbers, a point that holds a NaN, and a + vector index identifier again when the value is stored in a different + form or the NaN bits differ. + [#4895](https://github.com/memgraph/memgraph/pull/4895) - A node with more than one unique constraint no longer leaks memory when a write touches only one of them, including under `USING PERIODIC COMMIT` and `CALL { ... } IN TRANSACTIONS`. @@ -278,15 +314,42 @@ guide. rows, make writes in or before the `CALL` visible to later clauses, and expose the body's columns to `RETURN *`, `WITH *` and `CALL (*)`. [#4983](https://github.com/memgraph/memgraph/pull/4983) +- Rolling back a map-style property write (`SET n = {...}`, `SET n = {}`, + `SET n += {...}`, and the same on edges) keeps a vector-indexed + embedding and its index entry. + [#4991](https://github.com/memgraph/memgraph/pull/4991) +- A failed `CREATE VECTOR INDEX` or `CREATE VECTOR EDGE INDEX` (for + example a dimension mismatch) leaves every node and edge property + unchanged and readable. + [#4995](https://github.com/memgraph/memgraph/pull/4995) - `memgraph_show_instances_total` counts only `SHOW INSTANCES` queries. OpenMetrics scrapes no longer increment it. [#5005](https://github.com/memgraph/memgraph/pull/5005) +- After `RENAME DATABASE`, metrics scrapes report the new database name + without a restart. + [#5006](https://github.com/memgraph/memgraph/pull/5006) +- Memory from abandoned commits is reclaimed again, instead of growing + until restart. + [#5017](https://github.com/memgraph/memgraph/pull/5017) +- When an edge delete fails part-way and rolls back, the graph no longer + keeps duplicate edges. Traversals do not over-count them, and replicas + can apply the WAL without a full snapshot catch-up. + [#5105](https://github.com/memgraph/memgraph/pull/5105) +- `CREATE TRIGGER` refuses a statement that is not a Cypher query, such + as `CREATE USER` or `CREATE INDEX`. A trigger of that kind stored by an + earlier version is skipped with a warning at startup. + [#5108](https://github.com/memgraph/memgraph/pull/5108) {

🛠️ Improvements

} - Queries that set a timeout do less bookkeeping while they run, because the limit is checked against a shared clock instead of a per-query timer. [#4662](https://github.com/memgraph/memgraph/pull/4662) +- With `--experimental-enabled=commit-lock-narrowing`, a new transaction + can begin while a slow commit is still writing to the WAL or syncing to + replicas, and reads the last fully committed state instead of waiting. + The flag is off by default and can only be set at startup. + [#4685](https://github.com/memgraph/memgraph/pull/4685) - Edge property indexes now answer `STARTS WITH` from the index alone. `CONTAINS`, `ENDS WITH` and regex read only the string run of the index, not every value of the property. @@ -317,6 +380,9 @@ guide. A memory-limit error now reports how many partial paths were held and suggests a hop bound or `uniqueness: NODE_GLOBAL`. [#4875](https://github.com/memgraph/memgraph/pull/4875) +- `DISTINCT` and grouping over lists or maps that hold a null run about + twice as fast. Results are unchanged. + [#4892](https://github.com/memgraph/memgraph/pull/4892) - `CONTAINS`, `ENDS WITH` and regex on an indexed edge property are tested while reading the index, so non-matching values are dropped there rather than by a later filter. @@ -324,6 +390,10 @@ guide. - Cypher queries now parse faster the first time they run, especially relationships with a property map and `exists()` patterns. [#4963](https://github.com/memgraph/memgraph/pull/4963) +- A slow login (SSO, auth module, or password) no longer blocks queries + on other sessions. Permission checks during queries also wait less on + the auth lock. + [#4966](https://github.com/memgraph/memgraph/pull/4966) - Queries that evaluate expressions, including filters, run faster. [#5014](https://github.com/memgraph/memgraph/pull/5014)