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
32 changes: 31 additions & 1 deletion pages/memgraph-zero/memgql/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,37 @@ description: MemGQL release notes

# MemGQL Changelog

## MemGQL v0.1.0 - TODO
## MemGQL v0.10.0 - TODO

### 🍃 New features & Improvements

- **Cross-connector edges.** An edge declared with
`mappedJoinSource { "fromKey": …, "toKey": … }` links two labels in *different*
backends, so one pattern traverses the boundary:
`MATCH (c:Component)-[:MANUFACTURED_BY]->(m:Manufacturer)` reads components from
PostgreSQL and manufacturers from Memgraph. `RETURN c, r, m` packs real nodes and
a relationship, so graph clients can draw and expand the result. See
[Cross-connector edges](/memgraph-zero/memgql/schema-file#cross-connector-edges).
- **JSON / JSONB columns are queryable.** An attribute can declare a `path` into a
document column (`"column": "props", "path": "electrical.voltage", "type": "Double"`),
giving it its own typed property; or type the column `Json`, which returns it as a
map and keeps *undeclared* keys reachable as `c.props.rohs`. Both push the
extraction down to the source, and a declared type keeps comparisons numeric
rather than lexicographic. PostgreSQL, MySQL, DuckDB, SQL Server, Microsoft
Fabric and Snowflake. See
[JSON / JSONB columns](/memgraph-zero/memgql/schema-file#json--jsonb-columns).
- **TLS for PostgreSQL connections.** The mode stays in the URI (`sslmode=`, libpq
semantics); the connector adds `sslRootCert` (PEM bundle to trust instead of the
system roots) and `trustServerCertificate` (encrypt without verifying the server).
Built on rustls, so there's no OpenSSL to install. Declaring TLS settings
alongside `sslmode=disable` is refused rather than silently connecting in
plaintext. See
[TLS connections](/memgraph-zero/memgql/connect/postgres#tls-connections).
- **`SHOW STATS` reports query load per source.** One row per connector —
`queries`, `rows`, `errors`, `avg_latency_ms`, `max_latency_ms` — counting what
the source itself saw, so federation's impact on a production backend can be
measured. `RESET STATS` zeroes the counters. See
[Load per source](/memgraph-zero/memgql/multiple-graphs#load-per-source).

## MemGQL v0.9.0 - August 9th, 2026

Expand Down
67 changes: 67 additions & 0 deletions pages/memgraph-zero/memgql/connect/postgres.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,73 @@ MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name;

For environment variables, see [Reference](../reference.mdx#postgresql-postgres).

## TLS connections

For a cloud-hosted PostgreSQL, the connection **mode** goes in the connection
string as libpq's `sslmode=`, and the connector declares what a URI cannot
express — which certificates to trust:

```json
{
"name": "pg",
"type": "postgres",
"connection": {
"uri": "postgresql://user:pass@db.example.com:5432/app?sslmode=require",
"sslRootCert": "/etc/ssl/certs/customer-ca.pem",
"trustServerCertificate": false
}
}
```

| Field | Description |
|-------|-------------|
| `sslmode=` (in `uri`) | libpq semantics, including the default: `disable` never attempts TLS, `prefer` uses it when it works, `require` makes it mandatory. |
| `sslRootCert` | Path to a PEM bundle of CA certificates to trust **instead of** the system roots. Omit it to use the system trust store. |
| `trustServerCertificate` | Encrypt without verifying the server's identity. |

TLS is implemented with [rustls](https://github.com/rustls/rustls), so there is
no OpenSSL or other C library to install.

`trustServerCertificate` protects against passive eavesdropping but **not**
against an active man-in-the-middle, and it logs a warning on every use. It
exists because managed instances are routinely fronted by a certificate that
doesn't match the hostname you dial; prefer `sslRootCert` where you can. The two
are mutually exclusive — a CA bundle plus "trust anything" is a contradiction
and is rejected.

Two more guardrails:

- Declaring TLS settings alongside `sslmode=disable` is **refused** rather than
silently connecting in plaintext.
- Under `sslmode=prefer`, a failed handshake retries in plaintext and warns — so
enabling TLS support doesn't strand a connector pointed at a server whose
certificate you have no reason to trust. A connector that *declared*
`sslRootCert` or `trustServerCertificate` never falls back; it named a trust
anchor, so the failure is an error. Under `require` it is always an error.

These fields round-trip through `EXPORT SCHEMA`, so a dumped and reloaded
catalog reconnects with the same trust settings.

## JSONB columns

A `JSONB` column can be mapped as typed properties at fixed paths, or passed
through as a document whose undeclared keys stay queryable:

```json
"attributes": [
{ "name": "voltage", "column": "props", "path": "electrical.voltage", "type": "Double" },
{ "name": "props", "type": "Json" }
]
```

```gql
MATCH (c:Component) WHERE c.voltage > 100 RETURN c.sku, c.props.rohs;
```

Both forms push the extraction down to PostgreSQL, and a declared `type` makes
the comparison numeric instead of lexicographic. See
[Schema File → JSON / JSONB columns](/memgraph-zero/memgql/schema-file#json--jsonb-columns).

## Supported GQL features

| Feature | Postgres |
Expand Down
72 changes: 70 additions & 2 deletions pages/memgraph-zero/memgql/multiple-graphs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,10 @@ follow-up read routes without needing a `REFRESH SCHEMA`.
- A graph bound to a **non-default remote database** (Memgraph multi-tenancy) is
fenced to explicit `USE`; per-tenant introspection isn't wired up yet.
- A **single `MATCH` pattern that spans two backends** errors with guidance to
split it into one `MATCH` clause per graph; auto-splitting one pattern is out
of scope.
split it into one `MATCH` clause per graph — unless the link between them is
declared as a
[cross-connector edge](#traversing-across-backends-with-an-edge), which is
split automatically.
- A label-less `MATCH (n)` against a SQL backend still surfaces a raw backend
error (there's nothing to route or translate by).

Expand Down Expand Up @@ -255,6 +257,46 @@ or without it. It engages for backends registered as catalog graphs
locally. If the selective side matches nothing, the other backend is never
queried.

### Traversing across backends with an edge

The joins above are written as predicates over two query parts. A graph can
instead declare the link as a **cross-connector edge**, so the same join is
traversed as one pattern:

```json
{
"label": "MANUFACTURED_BY",
"from": "Component",
"to": "Manufacturer",
"mappedJoinSource": { "fromKey": "manufacturer_code", "toKey": "code" }
}
```

With `Component` mapped to PostgreSQL and `Manufacturer` to Memgraph, one
`MATCH` now spans both:

```gql
-- Instead of: USE pg_graph MATCH (c:Component) USE mg_graph MATCH (m:Manufacturer)
-- WHERE c.manufacturer_code = m.code
MATCH (c:Component)-[:MANUFACTURED_BY]->(m:Manufacturer)
WHERE c.voltage > 100
RETURN c.sku, m.name;
```

MemGQL rewrites the pattern into the federated join described above — one part
per connector plus the join equality — so piping, per-part caching and the local
join all apply unchanged. Returning whole elements works too, which is what a
graph client needs to draw and expand the result:

```gql
MATCH (c:Component)-[r:MANUFACTURED_BY]->(m:Manufacturer) RETURN c, r, m;
```

The edge must be traversed in a direction, matched by a single `MATCH` with one
path pattern, and carries no properties of its own. See
[Schema File → Cross-connector edges](/memgraph-zero/memgql/schema-file#cross-connector-edges)
for the full rules.

## Composite Queries Across Graphs

The GQL standard defines composite expressions combining query branches with `UNION`, `INTERSECT`, and `EXCEPT`. Each branch can target a different graph.
Expand Down Expand Up @@ -367,6 +409,32 @@ SHOW GRAPH social;
mode). To see what each one actually *defines* (the labels, relationship types,
and properties used for routing), use [`SHOW SCHEMA`](#schema-discovery).

### Load per source

Federated queries fan out to several backends, so it helps to know how much each
one is actually being asked to do. `SHOW STATS` reports that per connector:

```gql
RESET STATS;
MATCH (c:Component)-[:MANUFACTURED_BY]->(m:Manufacturer) RETURN c.sku, m.name;
SHOW STATS;
```

```
+--------+---------+------+--------+----------------+----------------+
| source | queries | rows | errors | avg_latency_ms | max_latency_ms |
+--------+---------+------+--------+----------------+----------------+
| mg | 1 | 2 | 0 | 6.42 | 6.42 |
| pg | 1 | 4 | 0 | 9.18 | 9.18 |
+--------+---------+------+--------+----------------+----------------+
```

Counts are what each **source** saw: statements dispatched to it and rows it
returned. `RESET STATS` zeroes the counters, so one query's cost on a production
backend can be measured in isolation. Cache hits and misses are keyed by graph
rather than connector and live in
[`SHOW GRAPH CACHES`](#caching-a-graph-in-memgraph).

## Graph Lifecycle Management

### Creating graphs
Expand Down
27 changes: 27 additions & 0 deletions pages/memgraph-zero/memgql/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,34 @@ SHOW MAPPINGS; -- per-graph mappings
SHOW SCHEMA [FOR <graph>]; -- unified routing index: labels, rel-types, properties
EXPORT SCHEMA [TO '<path>']; -- merged catalog as canonical schema JSON (round-trippable)
REFRESH SCHEMA; -- re-introspect live Cypher connections (Memgraph/Neo4j)

-- Query load per source
SHOW STATS; -- source, queries, rows, errors, avg_latency_ms, max_latency_ms
RESET STATS; -- zero the counters
```

`SHOW STATS` reports what each **source** saw — statements MemGQL dispatched to
it and rows it returned — so the load federation puts on a production backend
can be measured before rollout:

```
+--------+---------+------+--------+----------------+----------------+
| source | queries | rows | errors | avg_latency_ms | max_latency_ms |
+--------+---------+------+--------+----------------+----------------+
| mg | 3 | 6 | 0 | 6.42 | 11.03 |
| pg | 3 | 12 | 0 | 9.18 | 18.55 |
+--------+---------+------+--------+----------------+----------------+
```

Latency covers a whole query — the statement *and* the fetch of its rows — and
is reported in fractional milliseconds, since a healthy local source answers in
hundreds of microseconds. `max_latency_ms` sits next to the average because an
average hides the tail that shows up as a load problem. `RESET STATS` zeroes the
counters, so a single query's cost can be measured in isolation.

Counters are keyed by connector. **Cache** efficacy is keyed by graph and lives
in [`SHOW GRAPH CACHES`](/memgraph-zero/memgql/multiple-graphs#caching-a-graph-in-memgraph)
(hits, misses, resident fragments).

```
-- Single graph
Expand Down
125 changes: 122 additions & 3 deletions pages/memgraph-zero/memgql/schema-file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,17 @@ Every `connection` field is optional; supply what a given backend needs:
| `schema` | Middle namespace (PostgreSQL schema, MySQL / ClickHouse database, Iceberg schema). |
| `catalog` | Native root for three-level backends (Iceberg catalog, SQL Server database, Fabric warehouse/lakehouse item). |
| `path` | File-backed backends (DuckDB). |
| `sslRootCert` | PEM bundle of CA certificates to trust instead of the system roots (PostgreSQL). |
| `trustServerCertificate` | Encrypt without verifying the server's certificate (PostgreSQL). Mutually exclusive with `sslRootCert`. |

**Supported `type` values:** `memgraph`, `neo4j`, `postgres` (`postgresql`),
`mysql`, `sqlserver`, `oracle`, `duckdb`, `iceberg`, `iceberg-direct`,
`clickhouse`, `pinot`, `fabric`.
`clickhouse`, `pinot`, `snowflake`, `mongodb`, `fabric`.

For PostgreSQL, the TLS **mode** stays in the URI (`sslmode=`), where libpq
users expect it; `sslRootCert` / `trustServerCertificate` declare what a URI
cannot express. See
[TLS connections](/memgraph-zero/memgql/connect/postgres#tls-connections).

## `graphs`

Expand Down Expand Up @@ -231,7 +238,9 @@ A vertex maps a **label** to a backend source. Exactly one source kind is set:

An edge maps a **relationship type** between two labels. Relational edges name
the foreign-key columns via `metaFields.from` / `metaFields.to`; native-graph
edges pass through.
edges pass through; and a `mappedJoinSource` edge links two labels that live in
**different connectors** (see [Cross-connector edges](#cross-connector-edges)).
Exactly one source kind is set.

```json
{
Expand Down Expand Up @@ -280,6 +289,68 @@ A relational edge adds two more `metaFields` on top of `id`:
missing. Native-graph edges (`mappedGraphSource`) need only `from` / `to`
labels; the traversal is resolved by the backend.

#### Cross-connector edges

A third source kind, `mappedJoinSource`, declares an edge whose two endpoints
live in **different connectors**. It has no backing table anywhere: the
relationship *is* equality between one property on each endpoint.

```json
{
"label": "MANUFACTURED_BY",
"from": "Component",
"to": "Manufacturer",
"mappedJoinSource": {
"fromKey": "manufacturer_code",
"toKey": "code"
}
}
```

With `Component` mapped to a PostgreSQL table and `Manufacturer` to a Memgraph
connector, one pattern now traverses the boundary:

```gql
MATCH (c:Component)-[:MANUFACTURED_BY]->(m:Manufacturer) RETURN c.sku, m.name;
```

| `mappedJoinSource` key | Description |
|------------------------|-------------|
| `fromKey` | Property on the `from` vertex. A **GQL property name**, not a raw column; it resolves through that vertex's own mapping. |
| `toKey` | Property on the `to` vertex, resolved the same way. |

This is the only edge form that works when one endpoint is a native-graph
backend, which has no joinable edge table to point at. Under the hood the
traversal is rewritten into the
[cross-backend join](/memgraph-zero/memgql/multiple-graphs#focused-multi-graph-queries)
that already runs — one part per connector plus the join equality — so piping,
per-part caching and the local join all apply unchanged.

`RETURN c, r, m` packs real nodes and a relationship, so graph clients such as
Memgraph Lab can draw and expand the result. Element ids are synthesized from
the connector, label and key, so two backends that both number rows from `1`
don't collide.

**Rules and limits** (each is refused with a message saying what to write
instead):

- Both endpoints must live in different connectors — within one connector use
`mappedTableSource` with `metaFields.from` / `to`.
- Each key must be a declared `attribute` of its endpoint vertex (native-graph
vertices are exempt, since the query passes through).
- The edge must be traversed **in a direction**: `-[:R]->` or `<-[:R]-`, not
`-[:R]-`.
- No variable-length traversal, no `OPTIONAL MATCH`, and one `MATCH` with one
path pattern per query.
- The edge carries no properties, so it cannot be filtered — filter the
endpoints instead.
- Every node in the pattern needs a label, so each side can be routed.
- Aggregating *across* the join (for example `count()` over both sides) is not
supported yet; return the rows and count client-side.

A **table-backed** edge whose endpoints sit in different connectors is still
skipped at load time with a warning pointing at `mappedJoinSource`.

### Attributes

`attributes` declare the properties a label exposes.
Expand All @@ -289,9 +360,57 @@ labels; the traversal is resolved by the backend.
| `name` | yes | The GQL property name. |
| `column` | no | The backing column (defaults to `name`), e.g. property `name` ← column `full_name`. |
| `type` | no | Recorded, not enforced at query time (default `String`). |
| `path` | no | Location inside a JSON document column (see below). |

Allowed `type` values: `Boolean`, `Byte`, `Short`, `Int`, `Long`, `HugeInt`,
`Float`, `Double`, `Decimal`, `String`, `Date`, `DateTime`.
`Float`, `Double`, `Decimal`, `String`, `Date`, `DateTime`, `Json`.

#### JSON / JSONB columns

A document column (PostgreSQL `JSONB`, MySQL `JSON`, …) can be mapped two ways,
and they compose — use both on the same column.

**Declared path.** Pin a value inside the document to its own typed property.
`column` names the document column, `path` the location inside it, and `type`
the type of the value found there:

```json
{
"name": "voltage",
"column": "props",
"path": "electrical.voltage",
"type": "Double"
}
```

```gql
MATCH (c:Component) WHERE c.voltage > 240 RETURN c.voltage;
```

pushes down to the source as an extraction cast to the declared type. The cast
is what makes the comparison numeric — without it the database compares text,
where `'90' > '240'`. `path` is a dotted string; for keys that themselves
contain a dot, write an explicit segment list: `"path": ["spec.v2", "voltage"]`.

**Passthrough.** Mark the column `Json` and it stays a document: `RETURN
c.props` returns a map, and **undeclared** keys are reachable with ordinary
property syntax — `c.props.rohs`, `c.props.electrical.voltage` — each pushed
down as an extraction on the source. This is the option for heterogeneous blobs
where declaring every key up front isn't possible. An undeclared key carries no
declared type, so the comparison supplies one: compared against a number it is
cast like a declared numeric path, compared against a string it stays text. A
key the document doesn't have reads as `null` rather than failing the query.

```json
{ "name": "props", "type": "Json" }
```

**Limits.** `path` attributes are read-only — an `INSERT` through a JSON path is
rejected rather than clobbering the surrounding document. JSON path support is
per backend: PostgreSQL, MySQL, DuckDB, SQL Server, Microsoft Fabric and
Snowflake. A mapping that declares a `path` against any other connector fails to
load, so the mismatch surfaces at boot / `CREATE GRAPH` rather than on every
query.

`attributes` are optional and need not be exhaustive: a property you don't list
still resolves to a same-named column at query time (`p.sku` → column `sku`).
Expand Down