MemGQL Changelog

Note: behavior and breaking changes are tagged by how likely they are to affect existing queries and clients: BIG breaks common usage, MEDIUM affects some setups, and LOW rarely needs any action.

MemGQL v0.14.0 - October 25th, 2026

⚠️ Behavior and breaking changes

  • MEDIUM 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.
  • MEDIUM 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.
  • MEDIUM USE CONNECTION / USE CONNECTOR are refused. They now return an error pointing to USE <graph>.
  • LOW Result columns follow the RETURN order on Memgraph and Neo4j, also for statements forwarded verbatim and for empty results, which used to report a single result column. A write without RETURN reports no columns.
  • LOW Bolt connections close after 30 minutes of inactivity. They used to close 300 seconds after they were opened, however busy they were.
  • LOW A label-less MATCH (n) on a SQL backend returns an error listing the labels the mapping defines, instead of a raw backend error.
  • LOW Apache Pinot queries use SQL null semantics. MemGQL turns on Pinot’s null handling unless the connector already sets it, so a missing value is null instead of 0.
  • LOW A plain INSERT reports the affected count on DuckDB and Iceberg (Trino), which used to return the inserted row or a rows column.

🍃 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.
  • 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.
  • 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 and 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 as a correlated subquery on SQL backends. See Functions and reserved words.
  • 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 too, each join deciding on its own. See 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.
  • 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 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 and 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.
  • 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.
  • More of Cypher on every backend. UNION [ALL], CALL { … } subqueries, CALL proc(…) YIELD …, EXISTS { … } and pattern predicates (WHERE (p)-[:KNOWS]->(), WHERE NOT (p)-->()) parse in Cypher mode and run on SQL backends, where they used to be parse errors. Keywords work as names without backticks (MATCH (o:Order), n.end, SKIP $skip). On SQL backends toUpper, toLower, toString, toInteger, toFloat, toBoolean (and their OrNull forms), substring, split, rand, timestamp, id, labels, keys, properties and type are translated instead of being sent as unknown functions.
  • CALL { … } subqueries on SQL backends. MATCH (p:Person) CALL (p) { MATCH (p)-[:KNOWS]->(f) RETURN count(f) AS deg } RETURN p.name, deg runs on PostgreSQL, MySQL, DuckDB, Trino, SQL Server, Fabric and Oracle; other backends refuse it by name. In GQL the subquery used to be dropped silently.
  • More queries on iceberg-direct. Inline property maps (MATCH (p:Person {id: 1})), aggregates with or without grouping keys, OPTIONAL MATCH, WITH, UNWIND, and a MATCH that continues from a bound node now run in-process.
  • Faster iceberg-direct queries. IN lists, AND / OR, IS NULL, STARTS WITH, the keys of a content join and the ids an expansion reaches are pushed into the Iceberg scan, so fewer rows are read. The in-process interpreter matches IN lists and edge lookups by hash instead of scanning, and computes sort keys once per row. A filter on a thousand ids answers in under 100 ms instead of about 8 s.
  • Lower latency per query on one connection. Each Bolt reply is now sent without waiting for the client’s delayed acknowledgement, and a page of records is written in one go. Back-to-back queries on one connection no longer take about 40 ms each.
  • Configurable idle timeout. MEMGQL_SESSION_INACTIVITY_TIMEOUT (seconds, default 1800, 0 for never) sets how long a Bolt connection may stay idle. See Configuration Reference.

🐞 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 nulls (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(p.name) where name maps to full_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.
  • Large rows and parameters over Bolt. A row over 64 KB hung the client, and a parameter over about 16 KB was lost (Parameter $p not provided).
  • Cross-store expressions on a split vertex. An OR or arithmetic whose operands come from different stores (p.email = $e OR p.age = 99, pr.price * 1000.0 / pr.weight_g) failed with “not found in federation schema”.
  • count(*) on SQL backends was sent as COUNT().
  • substring() on SQL backends used Cypher’s 0-based offset as SQL’s 1-based one, returning the wrong slice (or an error on ClickHouse).
  • Zero-length paths. *0..n dropped the start node on SQL backends and MongoDB, and *0..0 failed.
  • OPTIONAL MATCH with a WHERE on the far node returned a null row per non-matching neighbour on SQL backends and MongoDB.
  • JSON values below a Json attribute on PostgreSQL and MySQL came back as text ("true", "3"), sorted as text, and failed in arithmetic.
  • A node carried through WITH failed in WHERE f IS NULL and in WITH p, count(f) followed by p.<key> on SQL backends.
  • Oracle errors show the ORA- code instead of “Oracle closed the connection without providing error details”.
  • MongoDB size() of a string failed, nested keys of a Json property (p.prefs.theme) were refused, UNWIND $ids AS i MATCH (p {id: i}) failed, and count(f) after OPTIONAL MATCH counted unmatched rows.
  • iceberg-direct returned columns under each other’s names when the table’s column order was not alphabetical, and a second MATCH on a bound node returned every edge of the table.
  • Edge INSERT after MATCH on SQL backends dropped the WHERE and the edge’s properties: MATCH (a), (b) WHERE … INSERT (a)-[:R {…}]->(b) failed on a NOT NULL key, or wrote an edge for every pair of nodes on ClickHouse and Iceberg (Trino). A MATCH followed by a new node, and a path INSERT without MATCH, now write every element.
  • INSERT … RETURN returned the affected-row count (the whole row on DuckDB) instead of the RETURN items on PostgreSQL, MySQL, DuckDB, Oracle, ClickHouse, Iceberg (Trino) and MongoDB.
  • WITH n on SQL backends failed with an ambiguous column when the key column is also listed in attributes.
  • Scalar functions per dialect. On Oracle %, char_length, left, right, log10 and CAST(… AS BOOLEAN) failed, and CAST(… AS FLOAT) returned unreadable bytes. On Iceberg (Trino) char_length, left and right failed, and on SQL Server round(x) failed. log(x) is the natural logarithm on every backend.
  • Value types. A DATE, TIME or TIMESTAMP column crashed the PostgreSQL connector, read as null on MySQL and as a day count on DuckDB; these and date('…') now return temporal values on PostgreSQL, MySQL, DuckDB, ClickHouse, Iceberg (Trino) and iceberg-direct. sum() over integers returned a decimal or text on PostgreSQL and MySQL, CAST(… AS BOOLEAN) returned 0 on MySQL, and CAST(… AS STRING) kept the number on DuckDB.
  • Apache Pinot returned 0 instead of null for NULLIF and for unmatched OPTIONAL MATCH values.
  • iceberg-direct refused scalar functions, NULLIF, CAST and the Cypher conversion functions; they now run in-process, and integer division truncates.

🚧 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.
  • An INSERT that writes several nodes and relationships on a SQL backend runs one statement per element and is not atomic; Oracle rolls back the earlier statements when a later one fails.

MemGQL v0.13.0 - October 7th, 2026

🍃 New features & Improvements

  • Multi-label vertex mappings. A vertex may declare "labels": ["Cat", "Animal"] instead of a single label. The first label identifies the mapping; the others may be shared with other mappings. A pattern on a shared label (MATCH (a:Animal)) reads every table that carries it as one UNION inside the connector, resolving shared property names through each table’s own columns and returning null for a property only some of them declare. Returned nodes carry all of their labels. See Vertices.
  • --bolt-address and --bolt-port. The Bolt listener’s interface and port as flags, alongside BOLT_LISTEN_ADDR; each flag replaces only its half of the address, so --bolt-port=7690 starts a second server beside the first. --bolt-address=127.0.0.1 keeps a server local. See Configuration.
  • Join edges within one connector. mappedJoinSource now also links two tables in the same relational connector — the case where a row carries the target’s business key rather than a foreign key into its metaFields.id, which a table-backed edge cannot express. The traversal is pushed down as a single JOIN, so aggregates across it work and no federated join is involved. See Cross-connector edges.
  • Usage telemetry. MemGQL sends anonymous usage data to Memgraph: a start and shutdown record with basic host facts and, every hour, the peak number of connectors, connections and graphs plus the number of mappings. It never includes your data, schema, or queries. Start the server with --telemetry-enabled=false to turn it off. See Telemetry.

⚠️ Behavior changes

  • MemGQL listens on every interface by default. The Bolt listener binds 0.0.0.0:7688 instead of 127.0.0.1:7688, matching Memgraph’s own --bolt-address default, so a published container port or a Kubernetes Service reaches it without BOLT_LISTEN_ADDR. Anything that can route to the host can now open a session: set --bolt-address=127.0.0.1 (or BOLT_LISTEN_ADDR=127.0.0.1:7688) where the server should stay local, and the server.listen_addr row of SHOW CONFIG reports what it bound.
  • A label the mapping does not declare now matches nothing. MATCH (p:Person&Vip) (GQL; (p:Person:Vip) in Cypher) on a Person table that does not carry Vip returns no rows. Previously the second label was ignored and every Person came back.

🐞 Bug fixes

  • SHOW STATS CONNECTORS missed queries that routed by label to a single connector; only federated and USE-qualified queries were counted.

🚧 Known limitations

  • A shared label is unioned within one connector only; shared across connectors it is refused by routing as ambiguous. A node matched through a shared label cannot be traversed from, INSERTed, or have JSON keys read through it — each refusal names the identifying label to use instead. See Known limitations.
  • A join edge inside one connector cannot be traversed with a variable-length step, and both endpoints on a Memgraph or Neo4j connector are refused in favour of mappedGraphSource.

MemGQL v0.12.0 - September 14th, 2026

⚠️ Breaking changes

  • SHOW STATS is now SHOW STATS CONNECTORS, and its first column is connector rather than source. Bare SHOW STATS returns the list of targets instead of the per-connector table. RESET STATS is unchanged.
  • SHOW LICENSE reports only the license. Its rows are offering (community or enterprise), status (valid, expired or unsupported), organization, valid_until, days_left, limit.max_cores, limit.max_connectors and limit.max_connections; numbers come back as integers, not strings. The old mode, core_limit, max_connectors and max_connections keys are renamed as above, license_type is dropped, and the compute_* / host_cores rows moved to SHOW CONFIG and SHOW STATS COMPUTE.
  • SHOW CONNECTORS gains a connections column — open connections per connector — after graph.

🍃 New features & Improvements

  • License core limit. A license may carry a core limit. MemGQL then sizes its query workers, the embedded DuckDB engine and the native Iceberg runtime to it, and at most that many queries do in-process compute at once. When every core is busy a query waits up to MEMGQL_COMPUTE_QUEUE_TIMEOUT_MS for a free one, then fails with Compute limit reached and can be retried. Community mode is capped at 2 cores. MEMGQL_COMPUTE_CORES lowers the budget further but never raises it. See Compute.
  • SHOW CONFIG. The server’s effective settings as name/value rows: the compute budget (compute.cores, compute.host_cores, compute.queue_timeout_ms), server flags (server.default_language, server.log_level, server.schema_file, …) and the license inputs, key masked.
  • SHOW STATS COMPUTE. Live compute counters — cores busy, queued, peak and refused, queries in flight, the server’s own CPU time — as typed key/value rows, in every connector mode. Bare SHOW STATS lists the available targets.
  • Composite keys. metaFields.id, from and to accept a list of columns ("id": ["region", "order_no"]) on every SQL backend. Edges join column by column at the source; RETURN n, WITH n and count(DISTINCT n) treat the tuple as one identity. EXPORT SCHEMA round-trips the list form. See Schema file.
  • SQL Server views across databases. mappedTableSource.database renders a three-part database.schema.object name, so one connection reaches views in other databases of the instance.
  • SQL Server lock hints. NoLock=true in the connection string puts WITH (NOLOCK) on every table MemGQL reads; IsolationLevel=<level> sets the session isolation level once. See SQL Server.
  • Memgraph databases from the schema file. On a memgraph or neo4j connector, connection.database selects the Bolt database — the same thing ADD CONNECTOR … GRAPH <db> selects at runtime — so one Memgraph Enterprise instance can back several connectors, one per tenant. EXPORT SCHEMA writes a runtime GRAPH selector back as database, so an exported schema reconnects to the same database. See Schema file.

🐞 Bug fixes

  • SQL Server dates. date, datetime, datetime2, smalldatetime and datetimeoffset columns come back as ISO-8601 text (2026-01-12, 2026-01-12T08:05:00-05:00) instead of an internal representation.
  • Whole-node results with string keys. RETURN n for a node whose key is a string column, or a composite key, reaches Bolt clients as a Node with an integer id; it used to fail the connection with a wire error on every SQL backend.

MemGQL v0.11.0 - September 2nd, 2026

🍃 New features & Improvements

  • Cypher queries. MemGQL speaks Cypher alongside GQL: per query with the Neo4j-style CYPHER [25] prefix, per session with SET SESSION LANGUAGE CYPHER, per connector with LANGUAGE 'cypher', or server-wide with --default-language. On Memgraph/Neo4j the text executes verbatim — an existing corpus keeps working unchanged, CALL subqueries, pattern predicates and all; on every other backend the same Cypher runs through the regular plan/translation path. The default stays GQL. See Query languages.
  • Read-only graphs. "accessMode": "readOnly" (schema file) or ALTER GRAPH <name> SET READ ONLY refuses any write to that graph before a backend sees it. The flip persists to the boot --schema file, so a restart cannot quietly reopen a replica. See Access modes.
  • Source onboarding through the engine. DESCRIBE CONNECTOR reads a source’s live catalog — tables, columns, types, keys (PostgreSQL, SAP HANA); GENERATE MAPPING FOR CONNECTOR … [TO '<path>'] turns it into a draft mapping (tables → vertices, foreign keys → edges) for review — loading stays explicit, and anything unrepresentable is named in _comment. See Onboarding a source.
  • The schema is a graph. SHOW SCHEMA [FOR <graph>] AS GRAPH returns the merged schema as Bolt nodes and relationships (cross-connector join edges marked), so agents plan federated queries by navigating it and graph UIs draw it. EXPORT SCHEMA now round-trips join edges and accessMode.

MemGQL v0.10.0 - August 16th, 2026

🍃 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.
  • 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.
  • 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.
  • 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.
  • SAP HANA connector. ADD CONNECTOR … TYPE hana URI 'hdbsql://host:39017' brings SAP HANA 2.0, HANA Cloud and HANA Express into the federation, reads and writes both, over a pure-Rust driver — no ODBC, no JDBC, no SAP client installation. TLS is chosen by the URL scheme (hdbsqls://), so HANA Cloud needs nothing beyond the s. A mapping written for PostgreSQL works against HANA unchanged. See Connect to SAP HANA.

MemGQL v0.9.0 - August 9th, 2026

🍃 New features & Improvements

  • Microsoft Fabric connector. New fabric connector type translates GQL to T-SQL over a Fabric Warehouse’s SQL endpoint — a pure-Rust driver, so there’s no ODBC layer to install. The same connector also reaches Lakehouse SQL analytics endpoints, SQL databases in Fabric, and mirrored databases, which need no connector of their own. It uses the same mapping format as every other SQL backend, references tables with three-part item.schema.table names — so one query can join across items in a workspace — and supports both reads and writes. Authenticate with a Microsoft Entra ID access token or a service principal. Variable-length paths are rejected, since Fabric has no recursive queries. See the Microsoft Fabric connector page for setup.

  • MongoDB connector. New mongodb connector type brings a document store into the federation — the first backend that is neither SQL nor Cypher. Queries translate to MongoDB aggregation pipelines and run server-side: a single hop becomes $lookup, a variable-length hop $graphLookup, so unlike the SQL backends the unbounded * form works too. Labels map to collections and relationship types to their own collections, using the same mapping format as every other backend, and both reads and writes are supported, including DETACH DELETE. Connect with a standard connection string, so replica sets and MongoDB Atlas work as-is; UNION and scalar functions inside RETURN are the notable gaps. See the MongoDB connector page for setup.

⚠️ Behavior changes

  • A vertex’s or edge’s metaFields.id is now exposed as a property. RETURN n / RETURN r include it, and n.<id_column> reads it by name — including after a WITH n boundary, which previously worked only when the column was called id.
  • The cache answers only for properties it holds. A fragment holds a label’s id and its declared attributes; a query reading anything else misses and reads the source, so results never differ from the uncached run. SHOW GRAPH CACHES gained hits, misses and cached_properties columns. See Caching a graph in Memgraph.

🐞 Bug fixes

  • ClickHouse returned result columns in alphabetical order rather than the order the query projected them. Names travelled with their values, so most clients were unaffected — but anything reading positionally was not.
  • The Memgraph cache returned different rows than its source. Cached edges pointed at the wrong endpoints or were missing, edge properties were never copied, and a cache holding only a label’s id answered property reads with NULL.
  • count(DISTINCT <node>) and count(DISTINCT <relationship>) produced invalid SQL on relational backends. Counting distinct properties was unaffected.
  • Grouping by a whole element produced invalid SQL — WITH n, count(…) and WITH r, count(…) on relational backends.
  • A warm table was re-scanned from the source whenever a newly-touched edge type pulled it in as an endpoint.
  • The cache’s warm-up log counted rows read from the source, reporting success even when no rows reached the cache.

MemGQL v0.8.0 - July 19th, 2026

⚠️ Breaking changes

  • New graph model: connectors + schema graphs. A connector is now a pure connection and a graph is a mapping (vertices + edges) laid over one or more connectors. Register a connection with ADD CONNECTOR, then define a graph with CREATE GRAPH <name> FROM '<json>' / FROM FILE '<path>', or boot the whole catalog from a single --schema file.
  • New mapping format. Mappings use vertices / edges with mappedTableSource (relational) or mappedGraphSource (native), metaFields (id / from / to), and attributes. The legacy nodes / edges format (id_column, source_label, rel_type) is no longer accepted; loading it raises an actionable error pointing at the new format.
  • Removed statements. ADD GRAPH … ON CONNECTOR, UNADD GRAPH, ALTER GRAPH SET CONNECTOR / GRAPH / MAPPING, ADD MAPPING / DROP MAPPING, and the connection-scoped CONNECT … AS, DISCONNECT, USE CONNECTION, and SET DEFAULT CONNECTION are gone. Register connectors + graphs instead and query by graph name (USE <graph>) or let routing pick the graph.

✨ New features & Improvements

  • Query graphs without USE. Schema-based routing infers the target graph from the labels, relationship types, and declared properties a query mentions; ambiguity is an actionable error. Explicit USE <graph> remains the override.
  • Caching engages under routing. A cache-enabled graph is populated and served whether a part is pinned with USE or routed by label. Re-issuing ALTER GRAPH … SET CACHE now drops stale fragments, so re-pointing the cache can’t serve a stale result.
  • Snowflake connector. New snowflake connector type translates GQL to Snowflake SQL over Snowflake’s HTTPS SQL API — a pure-Rust driver, so there’s no ODBC layer or Snowflake client to install. It uses the same mapping format as every other SQL backend, references tables with three-part database.schema.table names, and supports both reads and writes. Authenticate with a key-pair (JWT), a programmatic access token, or a password. Snowflake’s native Time Travel is exposed for point-in-time reads. See the Snowflake connector page for setup.

MemGQL v0.7.0 - July 5th, 2026

⚠️ Behavior changes

  • The info-queries-only log level is removed. --log-level=info-queries-only now fails at startup with the list of valid levels. Use --log-level=DEBUG to see queries and their transpiled output; plain INFO no longer logs query traffic.
  • The log file now honors --log-level. Previously the --log-file mirror received every line regardless of the console level; now both destinations get the same threshold-filtered stream.

✨ New features & Improvements

  • Memgraph as a query-driven cache. A catalog graph can now be cached in a Memgraph instance — aimed at data-warehouse connectors (Iceberg first) where a table is too large to materialize wholesale and every federated query otherwise re-scans it from object storage. Enable it per graph with a CACHE CONNECTOR clause:
    ADD CONNECTOR mg TYPE memgraph URI 'memgraph:7687';
    ADD GRAPH events ON CONNECTOR ice MAPPING warehouse READ ONLY
        CACHE CONNECTOR mg TTL 3600 MAX_BYTES 8G;
    The cache is populated by query execution itself (populate-on-read): the first read of a scope tees it into Memgraph (cache MISS), and later queries whose scans are covered are served entirely from Memgraph (cache HIT) with no source I/O. Because the cached data is a real graph in Memgraph, queries over cached scopes gain Memgraph’s full capability profile — variable-length expand, quantified patterns, graph algorithms — even when the source connector can’t express them. Toggle at runtime with ALTER GRAPH <g> SET CACHE CONNECTOR … / ALTER GRAPH <g> REMOVE CACHE, and inspect with SHOW GRAPH CACHES. TTL is in seconds; MAX_BYTES accepts a K/M/G/T binary suffix. Currently validated with Iceberg sources; the mechanism generalizes to the other warehouse connectors. See Multiple Graphs → Caching.
  • Cross-backend piping. Federated queries move far less data. When one side of a two-backend query is selective — it carries a literal property filter ({name: 'Memgraph'}), a LIMIT, or a bounded CALL — MemGQL runs that side first and uses its join keys to fetch only the matching rows from the other backend, instead of pulling the whole table and joining locally. Results are identical; queries that join a small, filtered set against a large remote table get faster. If the selective side matches nothing, the other backend isn’t queried at all. Piping applies to backends registered as catalog graphs (ADD GRAPH) and falls back to the previous behavior whenever it can’t apply. See Multiple Graphs.
  • Inline-map references across backends. A property inside a node’s inline map can now reference a binding from another MATCH part — and it pipes:
    MATCH (e:Employee)
    MATCH (i:Invoice {employee_id: e.id})
    RETURN e.name, i.total;
    Previously this join had to be spelled as WHERE i.employee_id = e.id. Works without USE (each part routes by its own labels) and with multiple keys ({country: o.country, city: o.city}).
  • SQL Server connector. New sqlserver connector type (aliases: mssql, sql_server, sql-server) translates GQL to T-SQL. The driver is tiberius, a pure-Rust TDS implementation: no ODBC or driver install needed. It uses the same mapping format as every other SQL backend and is registered in multi mode with an ADO-style connection string:
    ADD CONNECTOR mssql TYPE sqlserver
        URI 'Server=localhost,1433;Database=test;User Id=sa;Password=…;TrustServerCertificate=true'
        MAPPING social;
    There is no standalone CONNECTOR_TYPE=sqlserver environment mode yet — SQL Server is multi-mode only. Works today: matching and filters, aggregates (incl. COUNT(DISTINCT …)), arithmetic, CASE, COALESCE / NULLIF, string predicates, IN, OPTIONAL MATCH, WITH pipelines (incl. whole-node carry-through), UNION, and typed whole-node / whole-relationship projections. See the SQL Server connector page for setup and for what’s not there yet (variable-length paths, collect(), map projections).
  • ADD GRAPH IF NOT EXISTS. Re-registering an existing graph with the new IF NOT EXISTS clause succeeds as a no-op instead of erroring — for re-runnable setup scripts. Plain ADD GRAPH still errors on a duplicate name.
  • Log levels. --log-level now accepts the standard severity ladder: CRITICAL, ERROR, WARNING, INFO (default), DEBUG, TRACE (case-insensitive). A level also emits everything more severe. Incoming queries and the generated Cypher / SQL are logged at DEBUG. See Reference.

MemGQL v0.6.3 - June 21st, 2026

✨ New features & Improvements

  • Schema-based routing. In multi mode, queries no longer need a USE <graph> clause — the engine infers the backend from the query’s schema signals (labels, rel-types, properties) against a unified schema index, built from mappings for SQL backends and live introspection for Cypher backends. Routing is strict: exactly one candidate routes, zero or multiple hard-error with the fix; explicit USE always wins. Routed federated JOIN and UNION work too, and writes route on a unique candidate. Two new statements expose the index: SHOW SCHEMA [FOR <graph>] and REFRESH SCHEMA. Identifier matching is now exact engine-wide. Still gated to explicit USE: non-default remote databases (Memgraph multi-tenancy), a single MATCH pattern spanning two backends, and a label-less MATCH (n) against a SQL backend.
  • Native Apache Iceberg connector (iceberg-direct). A Trino-free Iceberg connector that reads tables directly via the REST catalog and Apache Arrow scans from object storage (S3/MinIO) — no SQL engine in the query path, with projection and predicate pushdown into the scan. It reuses the same mapping format as the Trino-backed iceberg connector and joins other backends in multi-connector federation. Register with ADD CONNECTOR <name> TYPE iceberg-direct URI '<rest-catalog-uri>' MAPPING <mapping>. Read-only — writes return an “unsupported” error.

MemGQL v0.6.2 - June 7th, 2026

✨ New features & Improvements

  • Typed projections on every SQL backend. RETURN p now returns a structured Bolt Node and RETURN r a typed Bolt Relationship on every SQL connector (PostgreSQL, MySQL, DuckDB, ClickHouse, Iceberg, Pinot, Oracle) — previously SQL backends returned the underlying column values and a map projection (RETURN p {.id, .name}) was required. Nodes carry their label and mapped properties; relationships carry their type and mapped properties. Mixing scalar and whole-element projections in one RETURN (RETURN p.name, p) works too.
  • Connection-less queries (liveness check). RETURN 1, RETURN 1 + 2 AS x and WITH 1 AS x RETURN x now evaluate locally with no connector configured and no backend reachable — useful as a Bolt-level health probe.
  • ORDER BY by RETURN alias in cross-backend queries. A sort key can reference a projected alias (RETURN fact.tx_count AS tx_count … ORDER BY tx_count), including when the alias is nested inside an arithmetic expression (ORDER BY tx_count + 1). The post-join sort rewrites the alias back to its underlying expression.
  • Delimited (quoted) identifiers in GQL queries now parse.

🐞 Bug fixes

  • PostgreSQL NUMERIC columns now deserialize correctly — whole and fractional values arrive at the Bolt driver as Float and NULL is preserved. Previously NUMERIC-typed columns were unsupported.

MemGQL v0.6.1 - May 31st, 2026

✨ New features & Improvements

  • Cross-backend joins (phase 1). Queries that USE two or more different graphs in a single statement now execute as a federated left-deep hash-join chain inside the Bolt server. LinearQuery carries parts: Vec<FocusedPart>; a multi-USE query lowers to a CrossGraphJoin chain of per-part RemoteScans. dispatch_cross_backend peels Limit > Sort > Distinct > Project > Filter into a FederationPipeline, dispatches each part via the per-backend BoltHandler::run_plan, materializes rows to canonical scalars, and folds via hash-join (SQL 3VL — NULL keys dropped) or Cartesian product when there’s no equi-predicate. Per-side materialization is capped at 1,000,000 rows.
    • Verified end-to-end: Memgraph, MySQL, PostgreSQL, DuckDB.
    • Wired but not yet verified end-to-end: Neo4j, Oracle, ClickHouse, Iceberg, Pinot — the integration is mechanically identical and awaits a broader live-backend test harness.
    • Composite (multi-column) equi-joins are supported.
    • Post-join Filter / Sort / Limit / Distinct and a federation expression evaluator run over the joined wide row, including cross-part arithmetic, string functions (toUpper, toLower, length, size, trim), and residual filters (STARTS WITH, comparisons).
    • Three-backend left-deep chains with skip-level predicates are supported.
    • Unsupported shapes return a typed error with an actionable headline: whole-node returns across the federation boundary, non-literal LIMIT/SKIP, unrecognized plan nodes above the join (Aggregate, Union), unsupported functions, etc.

🚧 Known limitations

  • Whole-node RETURN m across federation is rejected; CanonicalScalar::Node modeling is deferred. Reference individual properties instead (m.name).
  • Scalar functions reach the GQL parser catch-all today (the evaluator supports them — covered by unit tests — but the e2e path waits on a parser extension; aggregates and COALESCE / NULLIF go through the normal FnCall path).
  • No output-cardinality cap (only per-side input cap of 1,000,000 rows). Per-side dispatch is sequential; parallel fan-out is a follow-up.

MemGQL v0.6.0 - May 23rd, 2026

⚠️ Breaking changes

  • id_column is now required on every edge mapping. Previously optional (enforced at query time only for variable-length traversal), it must now be present on every edge entry in the mapping JSON. A mapping without id_column on an edge now fails at registration with Failed to parse mapping JSON: missing field 'id_column' at line N, whether the mapping is supplied via MAPPING_FILE at startup or via ADD MAPPING at runtime. Update existing mappings by adding the edge table’s primary key column to every edge — see the quick-start and connector examples for the new shape.
  • Untyped edge traversal ()-[]->(b) now errors on SQL backends. Previously this expanded into a UNION over candidate rel-type mappings and could silently over-count when label-distinct node tables shared numeric IDs. The translator now returns an actionable error explaining why untyped traversal isn’t safe on SQL backends and pointing users at either declaring the edge type or running on a Cypher backend (Memgraph, Neo4j). Cypher backends still accept ()-[]->() natively.

✨ New features & Improvements

  • Trail semantics for bounded variable-length on SQL backends. Patterns like (){1,3} now enforce the GQL DIFFERENT EDGES default — no edge is reused within a single matched path. The recursive CTE carries an _edges visited-set whose shape is per-dialect (Postgres ARRAY, MySQL JSON_ARRAY, DuckDB LIST). On cyclic graphs this matches Memgraph and Neo4j byte-for-byte where previously SQL backends returned extra rows.
  • COUNT(DISTINCT …) works end-to-end on every backend. Previously count(DISTINCT x) could leak through to backends as the synthetic COUNT_DISTINCT(...) function (no engine has that). Both Cypher and SQL translators now special-case count_distinct / collect_distinct / collect_list_distinct to emit the dialect-native COUNT(DISTINCT …).
  • GQL parse errors now surface the actual ANTLR diagnostic (line 1:N no viable alternative at input '...') instead of being swallowed into the generic No statements in GQL query message.
  • Cypher-style variable-length syntax ([:R*], [:R*1..3], [:R*1..]) now produces an actionable hint pointing at the GQL quantified-path-pattern form (-[:R]->()){1,3} instead of a confusing parse error.
  • Cross-graph parse errors (multiple USE <graph> clauses in one query) now return a clear “not yet supported” message instead of the generic parse-failure error.

🐞 Bug fixes

  • % (modulo) parses as a proper binary operator, not as a synthetic function call.
  • FOR x IN [...] retains the iterated list and binds x correctly (new UnwindClause AST node; planner emits LogicalPlan::Unwind). Execution is Cypher-only today — SQL backends parse and plan it, then return an actionable error. See the reference limitations for the SQL-side status.
  • NEXT query composition resolves names bound on the left-hand-side when the right-hand-side RETURN references them.
  • Rel-variable reuse across MATCH clauses parses without a redeclaration error.
  • RETURN 1’s internal _dummy placeholder no longer leaks into Cypher queries sent to native backends.

MemGQL v0.5.0 - May 16th, 2026

✨ New features & Improvements

  • Added Oracle connector (CONNECTOR_TYPE=oracle).
  • DuckDB connector joins as a fifth GQL-over-SQL backend (alongside Memgraph, Neo4j, PostgreSQL, MySQL).
  • OPTIONAL MATCH now works on SQL backends (PostgreSQL, MySQL, DuckDB) — previously Cypher-only.
  • WITH pipeline boundary (GQL scope D) on SQL backends — supports WITH, WITH DISTINCT, WITH … ORDER BY … LIMIT N, chained WITH … WITH …, and whole-node WITH n carry-through via derived-table SQL.
  • UNION / UNION ALL / UNION DISTINCT between query statements work across all five backends. Branches on the same backend translate to that backend’s native combinator; branches on different backends materialize locally and combine in-memory. - Map projections — RETURN n {.id, .title} AS info returns a Bolt Map (Memgraph, Neo4j, PostgreSQL, MySQL, DuckDB).
  • collect() aggregate returns a typed Bolt List (Memgraph, Neo4j, PostgreSQL, MySQL, DuckDB).
  • IN list-membership predicate — WHERE n.name IN ['Alice', 'Bob'].
  • STARTS WITH / ENDS WITH / CONTAINS string predicates portable across all backends.
  • Quantified path patterns (){m,n} on SQL backends emit a recursive CTE.
  • MATCH p = (…) RETURN p path binding works on Cypher backends and bounded-path SQL.
  • Unbounded variable-length paths (()-[*]->()) on SQL backends now return a clear error pointing at the bounded form ((){1,5}) or the Cypher fallback.
  • SHOW MAPPINGS / SHOW CONNECTORS error messages now hint at the correct setup statements (ADD MAPPING, ADD CONNECTOR).
  • Untyped edges ()-[]->(b) on SQL backends translate via a UNION ALL over candidate rel-type mappings.

🐞 Bug fixes

  • INSERT (a {…}) RETURN a.name no longer drops the RETURN clause.
  • % (modulo) operator now recognized in the grammar and routed through every translator.
  • PATH_LENGTH(p) on Cypher backends returns the integer length, not the relationship list.
  • Temporal types (date(...), LOCAL_DATETIME(...), etc.) arrive at the Bolt driver as proper Date / LocalDateTime structs (previously leaked as Rust debug-format strings).
  • RETURN column headers reflect the source expression text (n.age) instead of the literal placeholder "expr".
  • RETURN * no longer leaks internal Strategy-B _u / _e placeholders.
  • SKIP without LIMIT is now honored (was silently dropped).
  • NULL cells are sent as the PackStream 0xC0 byte (previously the string "NULL").
  • NULLIF and COALESCE work end-to-end on every backend.
  • OPTIONAL MATCH WHERE predicates inside the optional pattern land on the correct JOIN clause so unmatched outer rows survive Cypher’s semantics.

MemGQL v0.4.0 - May 7th, 2026

  • Added “Federated GQL Across Heterogeneous Backends” use case showing graph queries over ClickHouse and PostgreSQL
  • Added vector search capabilities (only Memgraph backend)
  • Fixed SET DEFAULT CONNECTION handling
  • Fixed flaky USE graph behavior and corrected USE graph routing
  • Fixed multi node and edge SQL INSERT
  • Fixed connection handling
  • Improved Trino startup wait
  • Fixed all tests under run_tests.sh

MemGQL v0.3.0 - April 26th, 2026

  • Added Apache Pinot connector support, including CONNECTION_TYPE=pinot single mode and multi-connection mode
  • Added MySQL connector support
  • Added multi-graph (USE graph …) and composite queries support

MemGQL v0.2.1 - April 17th, 2026

  • Fixed all required to make the Docker Compose example working as expected

MemGQL v0.2.0 - April 12th, 2026

  • Added MCP server
  • Added Clickhouse connector
  • Added the structured2graph agent to help generate mappings

MemGQL v0.1.0 - March 29th, 2026

  • GQL parser with ISO/IEC 39075 standard support including quantified path patterns
  • Federated Bolt server for querying across Neo4j, Memgraph, PostgreSQL, DuckDB, and Iceberg/Trino
  • GQL-to-native query translation (Cypher for graph DBs, SQL for relational)
  • Runtime connector management via ADD CONNECTOR, CONNECT, and USE statements
  • Shortest path queries with ALL SHORTEST, ANY SHORTEST, and SHORTEST k support