Supported GQL Query Features
What works today, split by backend category. “Cypher backends” means Memgraph and Neo4j (translation is largely passthrough); “SQL backends” means PostgreSQL, MySQL and DuckDB.
SQL Server, ClickHouse, Apache Iceberg, and Apache Pinot are also supported as connectors but with a narrower verified surface. See each connector’s page for the exact list of features each one supports.
| Feature | Cypher backends | SQL backends |
|---|---|---|
MATCH / WHERE / RETURN | ✓ | ✓ |
Pattern-level WHERE (MATCH (n WHERE …)) | ✓ | ✓ |
Multiple MATCH clauses in one query | ✓ | ✓ |
OPTIONAL MATCH (keep left side when no match found) | ✓ | ✓ |
WITH clause (chain query steps) | ✓ | ✓ |
WITH DISTINCT / WITH … ORDER BY … LIMIT N | ✓ | ✓ |
Multiple chained WITH steps in one query | ✓ | ✓ |
Pass a whole node through WITH n to a later step | ✓ | ✓ |
MATCH (n)-[r:R]->(m) typed edge expansion | ✓ | ✓ |
Untyped edge ()-[]->(b) (union over types) | ✓ | ✗ |
UNION / UNION ALL / UNION DISTINCT | ✓ | ✓ |
INTERSECT / EXCEPT | ✓ | ✓ |
Quantified path (){m,n} — bounded | ✓ | ✓ |
Quantified path (){m,} — unbounded | ✓ | ✗ |
Shortest-path (ALL SHORTEST / ANY SHORTEST / SHORTEST k) | ✓ | ✗ |
Whole-node RETURN n / whole-relationship RETURN r | ✓ | ✓ |
Map projections RETURN n {.id, .title} | ✓ | ✓ |
Connection-less RETURN 1 / RETURN 1 + 2 (liveness) | ✓ | ✓ |
IN list membership WHERE x IN […] | ✓ | ✓ |
STARTS WITH / ENDS WITH / CONTAINS | ✓ | ✓ |
collect() / collect_list() (aggregate) | ✓ | ✓ |
count, sum, avg, min, max | ✓ | ✓ |
COUNT(DISTINCT …) | ✓ | ✓ |
Arithmetic + - * / % | ✓ | ✓ |
CASE WHEN … THEN … ELSE … END | ✓ | ✓ |
COALESCE, NULLIF | ✓ | ✓ |
Temporals (date, datetime, localTime, …) | ✓ | ✗ |
INSERT (a {…}) RETURN a.x | ✓ | ✓ |
DELETE | ✓ | ✓ |
DETACH DELETE | ✓ | ✗ |
SET (property update) | ✓ | ✗ |
REMOVE (property delete) | ✓ | ✗ |
Known limitations
- Unbounded variable-length paths on SQL backends (
()-[*]->()) return an actionable error. - Untyped edge traversal on SQL backends (
MATCH ()-[]->(b)with no rel-type) returns an actionable error pointing users at declaring the edge type or running on a Cypher backend. The form is still accepted natively on Cypher backends. FOR x IN [...](UNWIND-style) on SQL backends returns an actionable error pointing users at running the query on a Cypher backend. The form is still accepted natively on Cypher backends.DETACH DELETEon SQL backends currently executes as a plainDELETE— relationship rows are not detached. It deletes a node that has no relationship rows and otherwise surfaces the backend’s constraint error; don’t rely on it.- Path variables on variable-length patterns —
MATCH p = (a){1,3}(b) RETURN pis not yet supported on SQL backends. Drop thep =binding (or query a Cypher backend) andRETURNthe individual nodes / edges instead.
Graph Management Query Syntax
ADD GRAPH [IF NOT EXISTS] <name> ON CONNECTOR <connector>
[GRAPH <remote_name>]
[MAPPING <mapping_name>]
[READ ONLY]
[CACHE CONNECTOR <cache_connector> [TTL <secs>] [MAX_BYTES <n>[K|M|G|T]]];
CREATE GRAPH <name>
[{ <gql_schema_body> } | ANY]
ON CONNECTOR <connector>;
UNADD GRAPH [IF EXISTS] <name>;
DROP GRAPH [IF EXISTS] <name>;
ALTER GRAPH <name> SET READ ONLY;
ALTER GRAPH <name> SET READ WRITE;
ALTER GRAPH <name> SET CONNECTOR <connector>;
ALTER GRAPH <name> SET GRAPH <remote_name>;
ALTER GRAPH <name> SET MAPPING <mapping_name>;
ALTER GRAPH <name> REMOVE MAPPING;
ALTER GRAPH <name> SET CACHE CONNECTOR <cache_connector> [TTL <secs>] [MAX_BYTES <n>[K|M|G|T]];
ALTER GRAPH <name> REMOVE CACHE;
SHOW GRAPH CACHES; -- graph, cache_connector, ttl_secs, max_bytes, fragment countADD GRAPH IF NOT EXISTS succeeds as a no-op when the name is already
registered — for re-runnable setup scripts; plain ADD GRAPH errors on a
duplicate name.
CACHE CONNECTOR makes a graph cache-enabled: its scans are populated into
a Memgraph cache connector on first read, and later covered queries are served
from that cache — see Multiple Graphs → Caching.
TTL is in seconds; MAX_BYTES accepts a plain byte count or a K/M/G/T
binary suffix (e.g. 8G).
SHOW SCHEMA [FOR <graph>]; -- unified routing index: labels, rel-types, properties
REFRESH SCHEMA; -- re-introspect live Cypher connections (Memgraph/Neo4j)-- Single graph
USE <graph> <query>;
-- Composite
USE <graph1> <query>
UNION | UNION ALL | INTERSECT | INTERSECT ALL | EXCEPT | EXCEPT ALL
USE <graph2> <query>;
-- USE-free: routes automatically when the query's labels / rel-types /
-- properties match exactly one registered source (see Multiple Graphs).
<query>;In multi mode, a query with no USE clause routes automatically when its
schema signals (labels, relationship types, properties) match exactly one
source; zero or multiple matches hard-error. Identifier matching is exact
(:person ≠ Person). Memgraph sources must run with --schema-info-enabled
for property-level introspection. See
Multiple Graphs → USE-free routing.
Configuration Reference
General
| Variable | Default | Description |
|---|---|---|
CONNECTOR_TYPE | memgraph | Connector to use (see table below) |
CONNECTION_TYPE | (none) | Alias for CONNECTOR_TYPE |
BOLT_LISTEN_ADDR | 127.0.0.1:7688 | Address the Bolt server binds to |
Logging
MemGQL writes log lines to the console (stdout for most levels, stderr for
ERROR and CRITICAL) and, in parallel, to a log file. Both destinations
receive the same stream, filtered by --log-level. Both are configured via
CLI flags on the Bolt server binary.
CLI Flags
| Flag | Default | Description |
|---|---|---|
--log-level=<LEVEL> | INFO | Logging verbosity for console and file (see below) |
--log-file=<PATH> | bolt_server.log | File to mirror the (level-filtered) log output to |
Log Levels
Levels form a severity ladder — picking a level also emits everything more
severe. Values are case-insensitive (--log-level=debug and
--log-level=DEBUG are the same; WARN is accepted for WARNING).
| Level | What it adds |
|---|---|
CRITICAL | Critical failures only |
ERROR | + errors |
WARNING | + warnings |
INFO | + connections, state changes, lifecycle events (default) |
DEBUG | + incoming queries and their transpiled Cypher / SQL |
TRACE | + plan and per-row execution detail |
An unknown level fails at startup with an actionable error listing the valid
values. The RUST_LOG environment variable is not consulted.
To see query traffic and what MemGQL translates it into, run with
--log-level=DEBUG. The info-queries-only value from earlier releases was
removed in v0.7.0 — see the
changelog.
Enterprise License
| Variable | Default | Description |
|---|---|---|
MEMGQL_ENTERPRISE_LICENSE | (none) | License key (mglk-...) |
MEMGQL_ORGANIZATION_NAME | (none) | Organization name to verify against license |
When set, the license is decoded and verified against the organization name at startup. A valid enterprise license removes connector and connection limits. Without a license, community mode allows up to 2 connectors and 2 simultaneous connections.
Connector Types
| Connector | Translation | Backend |
|---|---|---|
memgraph | None (passthrough) | Memgraph |
memgraph-gql | GQL -> Cypher | Memgraph |
neo4j | None (passthrough) | Neo4j |
neo4j-gql | GQL -> Cypher | Neo4j |
postgres | GQL -> SQL | PostgreSQL |
mysql | GQL -> SQL | MySQL 8.0+ |
oracle | GQL -> SQL | Oracle 19c+ (incl. Free 23ai) |
sqlserver | GQL -> SQL | Microsoft SQL Server (multi mode only) |
duckdb | GQL -> SQL | DuckDB (embedded) |
clickhouse | GQL -> SQL | ClickHouse |
iceberg | GQL -> SQL | Iceberg via Trino |
iceberg-direct | None (native in-process) | Iceberg (REST catalog + Arrow) |
pinot | GQL -> SQL | Apache Pinot |
multi | Per-connector | Multiple backends simultaneously |
Memgraph (memgraph, memgraph-gql)
| Variable | Default | Description |
|---|---|---|
MEMGRAPH_URI | 127.0.0.1:7687 | Connection URI |
MEMGRAPH_USER | user | Username |
MEMGRAPH_PASS | pass | Password |
MEMGRAPH_DB | memgraph | Database name |
Neo4j (neo4j, neo4j-gql)
| Variable | Default | Description |
|---|---|---|
NEO4J_URI | 127.0.0.1:7687 | Connection URI |
NEO4J_USER | neo4j | Username |
NEO4J_PASS | password | Password |
NEO4J_DB | neo4j | Database name |
PostgreSQL (postgres)
| Variable | Default | Description |
|---|---|---|
POSTGRES_URL | host=localhost user=postgres password=postgres dbname=postgres | libpq connection string |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
MySQL (mysql)
| Variable | Default | Description |
|---|---|---|
MYSQL_URL | mysql://root:mysql@localhost:3306/test | MySQL connection URL (mysql://user:pass@host:port/database) |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
Oracle (oracle)
| Variable | Default | Description |
|---|---|---|
ORACLE_URL | oracle://system:oracle@localhost:1521/FREEPDB1 | Easy Connect URL (oracle://user:pass@host:port/service_name). The default targets Oracle Database Free 23ai (service FREEPDB1). |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file (same format as Postgres) |
The Oracle connector uses oracle-rs, a pure-Rust implementation of Oracle’s TNS wire protocol, pooled via deadpool-oracle. No OCI / ODPI-C / Instant Client is required at build or runtime — the bundled Docker image ships only the bolt server binary plus TLS roots, and local builds work on macOS, Linux, and Windows without any extra system packages.
SQL Server (sqlserver)
SQL Server has no standalone environment-variable mode — CONNECTOR_TYPE=sqlserver
is not supported. Register it at runtime in
multi mode with an ADO-style
connection string:
ADD CONNECTOR mssql TYPE sqlserver
URI 'Server=localhost,1433;Database=test;User Id=sa;Password=YourPassword;TrustServerCertificate=true'
MAPPING <mapping>;mssql, sql_server, and sql-server are accepted aliases for the sqlserver
type. See the SQL Server connector page.
DuckDB (duckdb)
| Variable | Default | Description |
|---|---|---|
DUCKDB_PATH | :memory: | Path to DuckDB file |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
ClickHouse (clickhouse)
| Variable | Default | Description |
|---|---|---|
CLICKHOUSE_URL | http://localhost:8123 | ClickHouse HTTP API URL |
CLICKHOUSE_USER | default | ClickHouse user |
CLICKHOUSE_PASS | (none) | ClickHouse password |
CLICKHOUSE_DB | default | ClickHouse database |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
Apache Pinot (pinot)
| Variable | Default | Description |
|---|---|---|
PINOT_URL | http://localhost:8099 | Pinot broker base URL or full SQL endpoint |
PINOT_QUERY_OPTIONS | useMultistageEngine=true | Query options sent with broker SQL requests |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
Iceberg (iceberg)
| Variable | Default | Description |
|---|---|---|
TRINO_URL | http://localhost:8080 | Trino REST API URL |
TRINO_USER | trino | Trino user |
TRINO_CATALOG | iceberg | Trino catalog |
TRINO_SCHEMA | default | Trino schema |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
Iceberg Direct (iceberg-direct)
Native, in-process execution over Iceberg — reads the REST catalog and object storage (S3/MinIO) directly, no Trino. Read-only.
| Variable | Default | Description |
|---|---|---|
ICEBERG_REST_URI | http://localhost:8181 | Iceberg REST Catalog URI |
ICEBERG_WAREHOUSE | iceberg | Warehouse / catalog name |
ICEBERG_SCHEMA | default | Default namespace (schema) |
ICEBERG_DIRECT_S3_ENDPOINT | http://localhost:9000 | S3/MinIO endpoint |
ICEBERG_DIRECT_S3_REGION | us-east-1 | S3 region |
ICEBERG_DIRECT_S3_ACCESS_KEY_ID | admin | S3/MinIO access key |
ICEBERG_DIRECT_S3_SECRET_ACCESS_KEY | password | S3/MinIO secret key |
MAPPING_FILE | (none, uses built-in default) | Path to JSON mapping file |
S3 path-style access is always enabled (s3.path-style-access=true).
Mapping Schema
The graph mapping file is a JSON document with two top-level arrays:
nodes and edges. MemGQL loads it either from the path in MAPPING_FILE
at startup or via the ADD MAPPING <name> FROM '<path>' statement at
runtime — both paths apply the same validation. A mapping with any
required field missing is rejected before the server begins serving
queries against it.
{
"nodes": [ /* NodeMapping objects */ ],
"edges": [ /* EdgeMapping objects */ ]
}Node mapping fields
| Field | Type | Required | Description |
|---|---|---|---|
label | string | yes | GQL node label (e.g. "Person"). Matched exactly (case-sensitive). |
table | string | yes | Backend table or view name backing this label. |
id_column | string | yes | Primary key column. Surfaces as GQL id(n) and is used to join across edges. |
properties | object (string→string) | no | Map from GQL property name → backend column name. Properties not listed here pass through with the same name (p.name → column name). |
Connector-specific fields:
- Iceberg also accepts
catalogandschema_nameto fully qualify the table reference (defaults:"iceberg","default"). - ClickHouse also accepts
database(default:"default").
Edge mapping fields
| Field | Type | Required | Description |
|---|---|---|---|
rel_type | string | yes | GQL relationship type (e.g. "KNOWS"). Matched exactly (case-sensitive). |
table | string | yes | Junction or association table backing this edge type. |
id_column | string | yes | Per-edge primary key. Required since v0.6. Carried through the recursive CTE’s visited-edge set to enforce trail semantics on variable-length traversal. |
source_column | string | yes | FK column pointing to the source node’s id_column. |
target_column | string | yes | FK column pointing to the target node’s id_column. |
source_label | string | yes | Label of the source node (must match a label declared in nodes). |
target_label | string | yes | Label of the target node. |
properties | object (string→string) | no | Map from GQL property name → column name on the edge table. |
Connector-specific fields are the same as for nodes (catalog / schema_name
for Iceberg; database for ClickHouse).
Example
{
"nodes": [
{
"label": "Person",
"table": "persons",
"id_column": "id",
"properties": { "name": "full_name", "age": "age" }
},
{
"label": "Company",
"table": "companies",
"id_column": "id"
}
],
"edges": [
{
"rel_type": "KNOWS",
"table": "knows",
"id_column": "id",
"source_column": "from_id",
"target_column": "to_id",
"source_label": "Person",
"target_label": "Person"
},
{
"rel_type": "WORKS_AT",
"table": "works_at",
"id_column": "id",
"source_column": "person_id",
"target_column": "company_id",
"source_label": "Person",
"target_label": "Company"
}
]
}