Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions pages/memgraph-zero/memgql/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,180 @@ description: MemGQL release notes

# MemGQL Changelog

## MemGQL v0.14.0 - unreleased

### 🍃 New features & Improvements

- **Amazon Redshift connector.** `ADD CONNECTOR … TYPE redshift` maps
Redshift tables, on provisioned clusters and Serverless workgroups, as a
graph, with reads and writes pushed down as Redshift SQL. Authenticate with a
database password, or with IAM temporary credentials (`redshift+iam://…`)
fetched for your AWS identity on every connection. The endpoint from the AWS
console, or a `jdbc:redshift://` URL, works as the connector URI. See
[Amazon Redshift](/memgraph-zero/memgql/connect/redshift).
- **Schema discovery for native connectors.** A graph that lists a Memgraph or
Neo4j connector under `"discover": ["mg"]` reads that connector's schema at
boot and on `REFRESH SCHEMA` instead of repeating it: every node type becomes
a vertex (its label set spelled `CAT:Product`, properties as attributes),
every relationship type one edge per endpoint pair. Anything the graph
declares itself wins, so declaring an element is how you restrict what is
exposed. `EXPORT SCHEMA` writes `discover` back, not the discovered elements.
See [Discovered native schema](/memgraph-zero/memgql/schema-file#discovered-native-schema).
- **Multi-label native vertices and shared relationship types.** A native
vertex spelled `"label": "CAT:Product"` routes under each of its labels, so
`MATCH (p:CAT)`, `` MATCH (p:CAT&`Product`) `` and `CYPHER MATCH (p:CAT:Product)`
all reach it; a backticked `` :`CAT:Product` `` in a `CYPHER` passthrough is
sent as the label conjunction (it used to match nothing). A
`mappedGraphSource` edge label may be declared once per `from`/`to` pair,
and the declared pairs constrain a pattern end left unlabelled. `SHOW SCHEMA`
lists the declared or discovered attributes of native labels instead of
"(not introspected)". See [Vertices](/memgraph-zero/memgql/schema-file#vertices)
and [Edges](/memgraph-zero/memgql/schema-file#edges).
- **Query parameters are bound.** `MATCH (s:Store) WHERE s.region = $r …` with
`{r: 'North'}` sent by the driver now binds on every backend (as a prepared
placeholder on SQL, natively on Memgraph and Neo4j, including a `CYPHER`
passthrough). A parameter the client did not send is reported by name.
- **More of the expression language in GQL mode.** String and numeric
functions (`lower(s.name)`, `char_length`, `size`, `abs`, `floor`, …),
`CAST(x AS STRING | INT | FLOAT | BOOLEAN)`, concatenation with `||` or
`+`, and `EXISTS { … }` are translated per backend instead of being copied
into the backend query as text, so they work on renamed attributes and on
every SQL dialect (`CAST(x AS TEXT)`, `CONCAT`, `||`). On MongoDB the
functions with an aggregation operator (`upper`, `lower`, `trim`, `abs`,
`char_length`, `CAST`, …) now run as well, where every scalar function used
to be refused. `EXISTS { … }` runs natively on Memgraph and Neo4j and is
refused with a rewrite hint on SQL backends. See
[Functions and reserved words](/memgraph-zero/memgql/reference#functions-and-reserved-words-in-gql-mode).
- **Richer cross-backend queries.** A query joining `USE` blocks on different
backends can return whole nodes and relationships (`RETURN p, co`), and can
aggregate over the joined rows (`count`, `sum`, `min`, `max`, `avg`,
`count(DISTINCT …)`); the aggregate is partly pushed into each backend and
finished after the join. A `WITH` may follow the first `USE` block to
rename, filter or aggregate its rows before the next block runs. Joins on
several keys and chains of three or more blocks are now
[piped](/memgraph-zero/memgql/multiple-graphs#piping-fetching-only-what-joins)
too, each join deciding on its own. See
[Focused Multi-Graph Queries](/memgraph-zero/memgql/multiple-graphs#focused-multi-graph-queries).
- **Session defaults.** `SESSION SET GRAPH <graph>` gives the session a soft
default graph: it breaks ties when a label exists in several graphs and runs
queries with no label to route by, while an explicit `USE` still wins.
`SESSION SET CONNECTION <name>` names the fallback connection when no
default graph is set. Both last until `SESSION RESET …` or the end of the
session. See
[Graph Management Query Syntax](/memgraph-zero/memgql/reference#graph-management-query-syntax).
- **`max_in_list_size` per connector.** `ADD CONNECTOR … OPTIONS
'{"max_in_list_size": N}'` (schema file key `maxInListSize`) caps how many
join-key values one piped cross-backend join sends to that backend; above it
the join runs unpiped.
- **Re-running `ADD CONNECTOR` is safe.** An identical declaration replies
`already exists (unchanged)`; a changed one reconnects and replies
`replaced (changed: uri, password)`, naming the changed options but never
their values.
- **`INTERSECT` / `EXCEPT` on more backends.** PostgreSQL, MySQL, DuckDB,
ClickHouse and MongoDB now run them; on MongoDB MemGQL runs each arm and
combines the results itself. A `UNION`,
`INTERSECT` or `EXCEPT` over a cache-enabled graph checks each arm against
the [cache](/memgraph-zero/memgql/multiple-graphs#caching-a-graph-in-memgraph)
on its own.
- **`CALL` on PostgreSQL and MySQL.** `CALL fn(…) YIELD …` reads a PostgreSQL
set-returning function, and `CALL proc(…) YIELD …` the first result set of a
MySQL stored procedure. See [PostgreSQL](/memgraph-zero/memgql/connect/postgres)
and [MySQL](/memgraph-zero/memgql/connect/mysql).
- **Renaming native labels.** `mappedGraphSource.label` exposes a Memgraph or
Neo4j label or relationship type under another name: queries use the
exposed name, and returned nodes and relationships carry it. See
[Vertices](/memgraph-zero/memgql/schema-file#vertices).
- **More list and map results.** `collect()` runs on ClickHouse
(`groupArray`) and Oracle (`JSON_ARRAYAGG`, without `DISTINCT`), and map
projections on Oracle. On SQL Server `INSERT … RETURN` returns the inserted
values instead of a row count.
- **Large results are paged.** MemGQL answers the driver's fetch size and
signals `has_more`, so a result of any size reaches the client page by page.
- **Concurrent queries.** Queries to the same backend no longer wait for each
other; each connection keeps its own pool of handlers.

### ⚠️ Behavior changes

- **A routed query no longer changes the session's connection.** A query that
routed to a graph by its labels used to become the session's default for the
next query with nothing to route by. Use `SESSION SET GRAPH` or
`SESSION SET CONNECTION` to set a default on purpose.
- **Unsupported writes and projections fail instead of doing something
else.** On SQL backends `DETACH DELETE`, `REMOVE` and `DELETE r` on a
relationship return an actionable error; `DETACH DELETE` used to run as a
plain `DELETE` and `DELETE r` deleted every row of the relationship table.
Map projections (`RETURN n {.a, .b}`) on SQL Server, SAP HANA and Redshift
return an actionable error instead of `null`.
- **A label-less `MATCH (n)` on a SQL backend** returns an error listing the
labels the mapping defines, instead of a raw backend error.
- **`USE CONNECTION` / `USE CONNECTOR`** return an error pointing to
`USE <graph>`.

### 🐞 Bug fixes

- **`collect()` / `collect_list()` without a grouping key on SQL backends.**
`MATCH (p:Person) RETURN collect(p.name) AS names` sent an invalid
`COLLECT(...)` call to PostgreSQL, MySQL, and DuckDB instead of their list
aggregate, so the query failed. It now returns a list, the same as when the
`RETURN` also has a grouping key. On ClickHouse, SQL Server, and SAP HANA,
every `collect()` form, including `collect(DISTINCT …)`, now fails with an
actionable error; `collect(DISTINCT …)` used to return `null` there.
- **`SET` and `REMOVE` on a `READ ONLY` graph.** They were not routed by the
labels they touch, so the read-only check never saw a graph and the write
went to whichever backend the session had used last. They now route like
`DELETE` and are refused before any backend, the same as `INSERT`.
- **`OPTIONAL MATCH` filters.** On Memgraph a `WHERE` on the `MATCH` before an
`OPTIONAL MATCH` was attached to the optional pattern, so every row came
back; on SQL backends `count(n)` after an `OPTIONAL MATCH … WHERE` counted
the rows the filter had turned into `null`s (`500` instead of `1, 0, 0`).
Each filter now stays with its own clause, and `count(n)` counts matched
elements only.
- **An unlabelled end of a native edge.** `(p:Product)-[:inGroup]->(g)` could
return nodes of labels the graph never declared; the edge's declared `to`
label (or labels) now constrains `g`.
- **`CONTAINS` / `STARTS WITH` / `ENDS WITH` inside `CASE` on SQL backends**
always fell through to `ELSE`: the pattern was emitted without wildcards.
- **Functions on renamed attributes** (`lower(s.name)` where `name` maps to
`store_name`) emitted the GQL name into the SQL. `CAST(x AS STRING)` lost its
spaces, `size()` passed through unchanged, `||` and `+` on strings were not
translated.
- **`n.id` on a table whose key is not called `id`** emitted a column `id`;
it now reads the `metaFields.id` column.
- **Variable-length paths over `varchar(n)` keys** failed with a recursive
CTE type mismatch on PostgreSQL.
- **A later `MATCH` that reaches an already bound variable only at its end**
(`MATCH (i:Item) … MATCH (s:Store)-[:stocks]->(i)`) referenced a table
before it was in scope; it now joins back to the bound variable.
- **`count(r)` of a relationship carried through `WITH`** referenced a column
the derived table did not have.
- **Parse errors inside a backticked label** (`` (p:`CAT:Product`) ``) no
longer carry an unrelated "`Product` is a reserved word" hint.
- **Oracle** queries failed on quoted identifiers, derived-table aliases and
`FLOAT` casts; `NUMBER` values came back as text, and integer division did
not truncate.
- **ClickHouse** `UNION` failed without `DISTINCT`, unmatched `OPTIONAL MATCH`
values came back as defaults instead of `null`, and integer division
returned a float.
- **DuckDB** dropped the fraction of a float division.
- **A `UNION` without `USE` across graphs** could return no rows; each arm is
now routed and its rows collected.
- **A `NEXT` chain** dropped the `USE` of its first part.
- **Whole-node `WITH n` carry-through** on SQL backends read columns the
derived table did not have.
- **Two Memgraph or Neo4j connections with different schemas** shared one
introspected schema; each connection is now introspected at boot.
- **Single-connector Cypher mode** ran only the first arm of `INTERSECT`,
`EXCEPT` and `OTHERWISE`.

### 🚧 Known limitations

- Whole paths cannot be returned across backends, and `collect()` is not
supported over a cross-backend join.
- Results are paged to the client, but each connector still reads its full
result before the first page.
- On ClickHouse, integer division needs the attribute typed as an integer
(`"type": "Int"`) in the mapping; otherwise it divides as floating point.

## MemGQL v0.13.0 - October 7th, 2026

### 🍃 New features & Improvements
Expand Down
1 change: 1 addition & 0 deletions pages/memgraph-zero/memgql/connect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description: MemGQL connector details and configuration.

- [ClickHouse](/memgraph-zero/memgql/connect/clickhouse)
- [DuckDB](/memgraph-zero/memgql/connect/duckdb)
- [Amazon Redshift](/memgraph-zero/memgql/connect/redshift)
- [Iceberg](/memgraph-zero/memgql/connect/iceberg)
- [Microsoft Fabric](/memgraph-zero/memgql/connect/fabric)
- [Memgraph](/memgraph-zero/memgql/connect/memgraph)
Expand Down
1 change: 1 addition & 0 deletions pages/memgraph-zero/memgql/connect/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export default {
"mysql": "to MySQL",
"sqlserver": "to SQL Server",
"pinot": "to Pinot",
"redshift": "to Amazon Redshift",
"snowflake": "to Snowflake",
}
10 changes: 10 additions & 0 deletions pages/memgraph-zero/memgql/connect/clickhouse.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,18 @@ For environment variables, see [Reference](/memgraph-zero/memgql/reference#click
| Whole-node `RETURN n` / whole-rel `RETURN r` | ✓ |
| Pattern-level `WHERE` (`MATCH (n WHERE …)`) | ✓ |
| Typed edge `(a)-[r:R]->(b)` | ✓ |
| `OPTIONAL MATCH` (unmatched values are `NULL`) | ✓ |
| Whole-node `WITH n` carry-through | ✓ |
| `ORDER BY` / `LIMIT` | ✓ |
| `DISTINCT` | ✓ |
| Map projections `RETURN n {.a, .b}` | ✓ |
| `collect()` aggregate | ✓ |
| `UNION` / `UNION DISTINCT` | ✓ |
| `INTERSECT` / `EXCEPT` | ✓ |
| Integer division (`5 / 2` is `2`)¹ | ✓ |
| `INSERT (a {…})` | ✓ |
| `DELETE` (via `ALTER TABLE … DELETE`) | ✓ |
| Boolean literals as 1/0 | ✓ |

¹ Integer division needs the property typed as an integer (`"type": "Int"`) in
the mapping; otherwise it divides as floating point.
2 changes: 1 addition & 1 deletion pages/memgraph-zero/memgql/connect/duckdb.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ For environment variables, see [Reference](/memgraph-zero/memgql/reference#duckd
| Typed edge `(a)-[r:R]->(b)` | ✓ |
| Untyped edge `()-[]->(b)` | ✗ |
| `UNION` / `UNION ALL` / `UNION DISTINCT` | ✓ |
| `INTERSECT` / `EXCEPT` | ✓ |
| Quantified path `(){m,n}` (bounded) | ✓ |
| Whole-node `RETURN n` / whole-rel `RETURN r` | ✓ |
| Map projections `RETURN n {.a, .b}` | ✓ |
Expand All @@ -104,7 +105,6 @@ For environment variables, see [Reference](/memgraph-zero/memgql/reference#duckd
| `DELETE` | ✓ |
| `SET` / `REMOVE` (property update / delete) | ✗ |
| `DETACH DELETE` | ✗ |
| `INTERSECT` / `EXCEPT` | ✗ |
| Quantified path `(){m,}` (unbounded) | ✗ |
| Shortest-path (`ALL SHORTEST` / `SHORTEST k`) | ✗ |

Expand Down
7 changes: 4 additions & 3 deletions pages/memgraph-zero/memgql/connect/fabric.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,10 @@ These are Fabric engine limits, not MemGQL gaps:
Warehouse does not support recursive queries. MemGQL rejects such a query with
a clear error rather than returning a partial answer — use a fixed number of
hops, or run it against a Cypher backend.
- **`collect()` and map projections** (`RETURN n {.a, .b}`) are not pushed down:
Fabric's `FOR JSON` must be the last operator in a statement and is invalid in
subqueries.
- **`collect()` and map projections** (`RETURN n {.a, .b}`) return an
actionable error: Fabric's `FOR JSON` must be the last operator in a statement
and is invalid in subqueries.
- **Temporal functions** (`date()`, `datetime()`, …) return an actionable error.
- **Generated ids are not returned** on insert. Fabric has no usable
`IDENTITY_INSERT`, so write a natural key and read the row back by it.
- **Column types** are limited to what a Fabric table can store: `nvarchar`,
Expand Down
3 changes: 1 addition & 2 deletions pages/memgraph-zero/memgql/connect/hana.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,7 @@ MAPPING_FILE=/data/mapping.json \
[cache the graph in Memgraph](/memgraph-zero/memgql/multiple-graphs#caching-a-graph-in-memgraph),
which answers the pattern from the cache.
- **`collect()`** is not emitted yet; the query fails with an actionable error.
- **Map projections** (`RETURN n {.a, .b}`) currently return `NULL` instead of
a map (no error is raised); don't rely on them on this backend.
- **Map projections** (`RETURN n {.a, .b}`) return an actionable error.
- **`INSERT … RETURN`** executes the insert but returns the affected-row count
instead of the projected values.

Expand Down
42 changes: 34 additions & 8 deletions pages/memgraph-zero/memgql/connect/iceberg.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,18 @@ tables = {
pa.table({"id": [1], "name": ["Acme Corp"]}),
),
"knows": (
pa.schema([("from_id", pa.int32()), ("to_id", pa.int32())]),
pa.table({"from_id": [1], "to_id": [2]}),
pa.schema([("id", pa.int32()), ("from_id", pa.int32()), ("to_id", pa.int32())]),
pa.table({"id": [1], "from_id": [1], "to_id": [2]}),
),
"works_at": (
pa.schema([("person_id", pa.int32()), ("company_id", pa.int32())]),
pa.table({"person_id": [1], "company_id": [1]}),
pa.schema([("id", pa.int32()), ("person_id", pa.int32()), ("company_id", pa.int32())]),
pa.table({"id": [1], "person_id": [1], "company_id": [1]}),
),
}

for name, (schema, data) in tables.items():
table = catalog.create_table_if_not_exists(f"default.{name}", schema=schema)
table.append(data)
table.append(data.cast(schema))
```

### 3. Start MemGQL
Expand Down Expand Up @@ -191,19 +191,40 @@ REST API. Trino performs joins, pruning, aggregation, and writes server-side.

### 1. Start Trino with Iceberg

Trino reads Iceberg through a catalog. This one uses the MinIO storage and REST
catalog from step 1 of the direct connector above, so start those first.

```bash
docker network create memgql-net
mkdir -p catalog
cat > catalog/iceberg.properties << 'EOF'
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=http://iceberg-rest-dev:8181
fs.native-s3.enabled=true
s3.endpoint=http://minio-dev:9000
s3.path-style-access=true
s3.region=us-east-1
s3.aws-access-key=admin
s3.aws-secret-key=password
EOF

docker run -d --rm \
--name trino-dev \
--network memgql-net \
-p 8080:8080 \
-v ./catalog/iceberg.properties:/etc/trino/catalog/iceberg.properties:ro \
trinodb/trino:latest
```

### 2. Seed data

Both connectors read the same tables, so skip this step if you already seeded
them for the direct connector.

```bash
# Wait until Trino accepts queries.
until docker exec trino-dev trino --execute 'SELECT 1' >/dev/null 2>&1; do sleep 2; done

docker exec -i trino-dev trino << 'SQL'
CREATE SCHEMA IF NOT EXISTS iceberg.default;

Expand All @@ -217,18 +238,20 @@ CREATE TABLE IF NOT EXISTS iceberg.default.companies (
name VARCHAR
);
CREATE TABLE IF NOT EXISTS iceberg.default.knows (
id INTEGER,
from_id INTEGER,
to_id INTEGER
);
CREATE TABLE IF NOT EXISTS iceberg.default.works_at (
id INTEGER,
person_id INTEGER,
company_id INTEGER
);

INSERT INTO iceberg.default.persons VALUES (1, 'Alice', 30), (2, 'Bob', 25);
INSERT INTO iceberg.default.companies VALUES (1, 'Acme Corp');
INSERT INTO iceberg.default.knows VALUES (1, 2);
INSERT INTO iceberg.default.works_at VALUES (1, 1);
INSERT INTO iceberg.default.knows VALUES (1, 1, 2);
INSERT INTO iceberg.default.works_at VALUES (1, 1, 1);
SQL
```

Expand Down Expand Up @@ -292,3 +315,6 @@ aggregation run **in-process** over Arrow batches.
| Time-travel (snapshot) | ✓ | ✓ |
| `INSERT (a {…})` | ❌ (read-only) | ✓ |
| `DELETE` (no alias; Trino requirement) | ❌ (read-only) | ✓ |

On the Trino connector, map projections (`RETURN n {.a, .b}`) and `collect()`
return an actionable error.
2 changes: 1 addition & 1 deletion pages/memgraph-zero/memgql/connect/memgraph.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ GQL-specific syntax (INSERT, LOCAL_DATETIME, etc.) only works on `memgraph-gql`;
| Typed edge `(a)-[r:R]->(b)` | ✓ |
| Untyped edge `()-[]->(b)` | ✓ |
| `UNION` / `UNION ALL` / `UNION DISTINCT` | ✓ |
| `INTERSECT` / `EXCEPT` | ✗ |
| `INTERSECT` / `EXCEPT` | ✓ |
| Quantified path `(){m,n}` — bounded | ✓ |
| Quantified path `(){m,}` — unbounded | ✓ |
| Shortest-path (`ALL SHORTEST` / `SHORTEST k`) | ✓ |
Expand Down
Loading
Loading