MemGQL Changelog
Note: behavior and breaking changes are tagged by how likely they are to affect existing queries and clients:
BIGbreaks common usage,MEDIUMaffects some setups, andLOWrarely needs any action.
MemGQL v0.14.0 - October 25th, 2026
⚠️ Behavior and breaking changes
MEDIUMUnsupported writes and projections fail instead of doing something else. On SQL backendsDETACH DELETE,REMOVEandDELETE ron a relationship return an actionable error;DETACH DELETEused to run as a plainDELETEandDELETE rdeleted every row of the relationship table. Map projections (RETURN n {.a, .b}) on SQL Server, SAP HANA and Redshift return an actionable error instead ofnull.MEDIUMA 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. UseSESSION SET GRAPHorSESSION SET CONNECTIONto set a default on purpose.MEDIUMUSE CONNECTION/USE CONNECTORare refused. They now return an error pointing toUSE <graph>.LOWResult columns follow theRETURNorder on Memgraph and Neo4j, also for statements forwarded verbatim and for empty results, which used to report a singleresultcolumn. A write withoutRETURNreports no columns.LOWBolt connections close after 30 minutes of inactivity. They used to close 300 seconds after they were opened, however busy they were.LOWA label-lessMATCH (n)on a SQL backend returns an error listing the labels the mapping defines, instead of a raw backend error.LOWApache Pinot queries use SQL null semantics. MemGQL turns on Pinot’s null handling unless the connector already sets it, so a missing value isnullinstead of0.LOWA plainINSERTreports the affected count on DuckDB and Iceberg (Trino), which used to return the inserted row or arowscolumn.
🍃 New features & Improvements
- Amazon Redshift connector.
ADD CONNECTOR … TYPE redshiftmaps 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 ajdbc: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 onREFRESH SCHEMAinstead of repeating it: every node type becomes a vertex (its label set spelledCAT: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 SCHEMAwritesdiscoverback, 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, soMATCH (p:CAT),MATCH (p:CAT&`Product`)andCYPHER MATCH (p:CAT:Product)all reach it; a backticked:`CAT:Product`in aCYPHERpassthrough is sent as the label conjunction (it used to match nothing). AmappedGraphSourceedge label may be declared once perfrom/topair, and the declared pairs constrain a pattern end left unlabelled.SHOW SCHEMAlists 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 aCYPHERpassthrough). 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+, andEXISTS { … }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
USEblocks 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. AWITHmay follow the firstUSEblock 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 explicitUSEstill wins.SESSION SET CONNECTION <name>names the fallback connection when no default graph is set. Both last untilSESSION RESET …or the end of the session. See Graph Management Query Syntax. max_in_list_sizeper connector.ADD CONNECTOR … OPTIONS '{"max_in_list_size": N}'(schema file keymaxInListSize) caps how many join-key values one piped cross-backend join sends to that backend; above it the join runs unpiped.- Re-running
ADD CONNECTORis safe. An identical declaration repliesalready exists (unchanged); a changed one reconnects and repliesreplaced (changed: uri, password), naming the changed options but never their values. INTERSECT/EXCEPTon more backends. PostgreSQL, MySQL, DuckDB, ClickHouse and MongoDB now run them; on MongoDB MemGQL runs each arm and combines the results itself. AUNION,INTERSECTorEXCEPTover a cache-enabled graph checks each arm against the cache on its own.CALLon PostgreSQL and MySQL.CALL fn(…) YIELD …reads a PostgreSQL set-returning function, andCALL proc(…) YIELD …the first result set of a MySQL stored procedure. See PostgreSQL and MySQL.- Renaming native labels.
mappedGraphSource.labelexposes 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, withoutDISTINCT), and map projections on Oracle. On SQL ServerINSERT … RETURNreturns 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 backendstoUpper,toLower,toString,toInteger,toFloat,toBoolean(and theirOrNullforms),substring,split,rand,timestamp,id,labels,keys,propertiesandtypeare 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, degruns 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 aMATCHthat continues from a bound node now run in-process. - Faster
iceberg-directqueries.INlists,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 matchesINlists 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, default1800,0for 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 namessent an invalidCOLLECT(...)call to PostgreSQL, MySQL, and DuckDB instead of their list aggregate, so the query failed. It now returns a list, the same as when theRETURNalso has a grouping key. On ClickHouse, SQL Server, and SAP HANA, everycollect()form, includingcollect(DISTINCT …), now fails with an actionable error;collect(DISTINCT …)used to returnnullthere.SETandREMOVEon aREAD ONLYgraph. 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 likeDELETEand are refused before any backend, the same asINSERT.OPTIONAL MATCHfilters. On Memgraph aWHEREon theMATCHbefore anOPTIONAL MATCHwas attached to the optional pattern, so every row came back; on SQL backendscount(n)after anOPTIONAL MATCH … WHEREcounted the rows the filter had turned intonulls (500instead of1, 0, 0). Each filter now stays with its own clause, andcount(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 declaredtolabel (or labels) now constrainsg. CONTAINS/STARTS WITH/ENDS WITHinsideCASEon SQL backends always fell through toELSE: the pattern was emitted without wildcards.- Functions on renamed attributes (
lower(p.name)wherenamemaps tofull_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.idon a table whose key is not calledidemitted a columnid; it now reads themetaFields.idcolumn.- Variable-length paths over
varchar(n)keys failed with a recursive CTE type mismatch on PostgreSQL. - A later
MATCHthat 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 throughWITHreferenced a column the derived table did not have.- Parse errors inside a backticked label (
(p:`CAT:Product`)) no longer carry an unrelated “Productis a reserved word” hint. - Oracle queries failed on quoted identifiers, derived-table aliases and
FLOATcasts;NUMBERvalues came back as text, and integer division did not truncate. - ClickHouse
UNIONfailed withoutDISTINCT, unmatchedOPTIONAL MATCHvalues came back as defaults instead ofnull, and integer division returned a float. - DuckDB dropped the fraction of a float division.
- A
UNIONwithoutUSEacross graphs could return no rows; each arm is now routed and its rows collected. - A
NEXTchain dropped theUSEof its first part. - Whole-node
WITH ncarry-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,EXCEPTandOTHERWISE. - 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
ORor 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 asCOUNT().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..ndropped the start node on SQL backends and MongoDB, and*0..0failed. OPTIONAL MATCHwith aWHEREon the far node returned anullrow per non-matching neighbour on SQL backends and MongoDB.- JSON values below a
Jsonattribute on PostgreSQL and MySQL came back as text ("true","3"), sorted as text, and failed in arithmetic. - A node carried through
WITHfailed inWHERE f IS NULLand inWITH p, count(f)followed byp.<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 aJsonproperty (p.prefs.theme) were refused,UNWIND $ids AS i MATCH (p {id: i})failed, andcount(f)afterOPTIONAL MATCHcounted unmatched rows. iceberg-directreturned columns under each other’s names when the table’s column order was not alphabetical, and a secondMATCHon a bound node returned every edge of the table.- Edge
INSERTafterMATCHon SQL backends dropped theWHEREand the edge’s properties:MATCH (a), (b) WHERE … INSERT (a)-[:R {…}]->(b)failed on aNOT NULLkey, or wrote an edge for every pair of nodes on ClickHouse and Iceberg (Trino). AMATCHfollowed by a new node, and a pathINSERTwithoutMATCH, now write every element. INSERT … RETURNreturned the affected-row count (the whole row on DuckDB) instead of theRETURNitems on PostgreSQL, MySQL, DuckDB, Oracle, ClickHouse, Iceberg (Trino) and MongoDB.WITH non SQL backends failed with an ambiguous column when the key column is also listed inattributes.- Scalar functions per dialect. On Oracle
%,char_length,left,right,log10andCAST(… AS BOOLEAN)failed, andCAST(… AS FLOAT)returned unreadable bytes. On Iceberg (Trino)char_length,leftandrightfailed, and on SQL Serverround(x)failed.log(x)is the natural logarithm on every backend. - Value types. A
DATE,TIMEorTIMESTAMPcolumn crashed the PostgreSQL connector, read asnullon MySQL and as a day count on DuckDB; these anddate('…')now return temporal values on PostgreSQL, MySQL, DuckDB, ClickHouse, Iceberg (Trino) andiceberg-direct.sum()over integers returned a decimal or text on PostgreSQL and MySQL,CAST(… AS BOOLEAN)returned0on MySQL, andCAST(… AS STRING)kept the number on DuckDB. - Apache Pinot returned
0instead ofnullforNULLIFand for unmatchedOPTIONAL MATCHvalues. iceberg-directrefused scalar functions,NULLIF,CASTand 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
INSERTthat 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 singlelabel. 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 oneUNIONinside the connector, resolving shared property names through each table’s own columns and returningnullfor a property only some of them declare. Returned nodes carry all of their labels. See Vertices. --bolt-addressand--bolt-port. The Bolt listener’s interface and port as flags, alongsideBOLT_LISTEN_ADDR; each flag replaces only its half of the address, so--bolt-port=7690starts a second server beside the first.--bolt-address=127.0.0.1keeps a server local. See Configuration.- Join edges within one connector.
mappedJoinSourcenow 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 itsmetaFields.id, which a table-backed edge cannot express. The traversal is pushed down as a singleJOIN, 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=falseto turn it off. See Telemetry.
⚠️ Behavior changes
- MemGQL listens on every interface by default. The Bolt listener binds
0.0.0.0:7688instead of127.0.0.1:7688, matching Memgraph’s own--bolt-addressdefault, so a published container port or a Kubernetes Service reaches it withoutBOLT_LISTEN_ADDR. Anything that can route to the host can now open a session: set--bolt-address=127.0.0.1(orBOLT_LISTEN_ADDR=127.0.0.1:7688) where the server should stay local, and theserver.listen_addrrow ofSHOW CONFIGreports what it bound. - A label the mapping does not declare now matches nothing.
MATCH (p:Person&Vip)(GQL;(p:Person:Vip)in Cypher) on aPersontable that does not carryVipreturns no rows. Previously the second label was ignored and everyPersoncame back.
🐞 Bug fixes
SHOW STATS CONNECTORSmissed queries that routed by label to a single connector; only federated andUSE-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 STATSis nowSHOW STATS CONNECTORS, and its first column isconnectorrather thansource. BareSHOW STATSreturns the list of targets instead of the per-connector table.RESET STATSis unchanged.SHOW LICENSEreports only the license. Its rows areoffering(communityorenterprise),status(valid,expiredorunsupported),organization,valid_until,days_left,limit.max_cores,limit.max_connectorsandlimit.max_connections; numbers come back as integers, not strings. The oldmode,core_limit,max_connectorsandmax_connectionskeys are renamed as above,license_typeis dropped, and thecompute_*/host_coresrows moved toSHOW CONFIGandSHOW STATS COMPUTE.SHOW CONNECTORSgains aconnectionscolumn — open connections per connector — aftergraph.
🍃 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_MSfor a free one, then fails withCompute limit reachedand can be retried. Community mode is capped at 2 cores.MEMGQL_COMPUTE_CORESlowers the budget further but never raises it. See Compute. SHOW CONFIG. The server’s effective settings asname/valuerows: 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. BareSHOW STATSlists the available targets.- Composite keys.
metaFields.id,fromandtoaccept a list of columns ("id": ["region", "order_no"]) on every SQL backend. Edges join column by column at the source;RETURN n,WITH nandcount(DISTINCT n)treat the tuple as one identity.EXPORT SCHEMAround-trips the list form. See Schema file. - SQL Server views across databases.
mappedTableSource.databaserenders a three-partdatabase.schema.objectname, so one connection reaches views in other databases of the instance. - SQL Server lock hints.
NoLock=truein the connection string putsWITH (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
memgraphorneo4jconnector,connection.databaseselects the Bolt database — the same thingADD CONNECTOR … GRAPH <db>selects at runtime — so one Memgraph Enterprise instance can back several connectors, one per tenant.EXPORT SCHEMAwrites a runtimeGRAPHselector back asdatabase, so an exported schema reconnects to the same database. See Schema file.
🐞 Bug fixes
- SQL Server dates.
date,datetime,datetime2,smalldatetimeanddatetimeoffsetcolumns 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 nfor 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 withSET SESSION LANGUAGE CYPHER, per connector withLANGUAGE 'cypher', or server-wide with--default-language. On Memgraph/Neo4j the text executes verbatim — an existing corpus keeps working unchanged,CALLsubqueries, 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) orALTER GRAPH <name> SET READ ONLYrefuses any write to that graph before a backend sees it. The flip persists to the boot--schemafile, so a restart cannot quietly reopen a replica. See Access modes. - Source onboarding through the engine.
DESCRIBE CONNECTORreads 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 GRAPHreturns 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 SCHEMAnow round-trips join edges andaccessMode.
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, mpacks 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
pathinto a document column ("column": "props", "path": "electrical.voltage", "type": "Double"), giving it its own typed property; or type the columnJson, which returns it as a map and keeps undeclared keys reachable asc.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 addssslRootCert(PEM bundle to trust instead of the system roots) andtrustServerCertificate(encrypt without verifying the server). Built on rustls, so there’s no OpenSSL to install. Declaring TLS settings alongsidesslmode=disableis refused rather than silently connecting in plaintext. See TLS connections. SHOW STATSreports 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 STATSzeroes 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 thes. 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
fabricconnector 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-partitem.schema.tablenames — 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
mongodbconnector 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, includingDETACH DELETE. Connect with a standard connection string, so replica sets and MongoDB Atlas work as-is;UNIONand scalar functions insideRETURNare the notable gaps. See the MongoDB connector page for setup.
⚠️ Behavior changes
- A vertex’s or edge’s
metaFields.idis now exposed as a property.RETURN n/RETURN rinclude it, andn.<id_column>reads it by name — including after aWITH nboundary, which previously worked only when the column was calledid. - 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 CACHESgainedhits,missesandcached_propertiescolumns. 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>)andcount(DISTINCT <relationship>)produced invalid SQL on relational backends. Counting distinct properties was unaffected.- Grouping by a whole element produced invalid SQL —
WITH n, count(…)andWITH 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 withADD CONNECTOR, then define a graph withCREATE GRAPH <name> FROM '<json>'/FROM FILE '<path>', or boot the whole catalog from a single--schemafile. - New mapping format. Mappings use
vertices/edgeswithmappedTableSource(relational) ormappedGraphSource(native),metaFields(id/from/to), andattributes. The legacynodes/edgesformat (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-scopedCONNECT … AS,DISCONNECT,USE CONNECTION, andSET DEFAULT CONNECTIONare 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. ExplicitUSE <graph>remains the override. - Caching engages under routing. A cache-enabled graph is populated
and served whether a part is pinned with
USEor routed by label. Re-issuingALTER GRAPH … SET CACHEnow drops stale fragments, so re-pointing the cache can’t serve a stale result. - Snowflake connector. New
snowflakeconnector 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-partdatabase.schema.tablenames, 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-onlylog level is removed.--log-level=info-queries-onlynow fails at startup with the list of valid levels. Use--log-level=DEBUGto see queries and their transpiled output; plainINFOno longer logs query traffic. - The log file now honors
--log-level. Previously the--log-filemirror 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 CONNECTORclause: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 withADD 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;ALTER GRAPH <g> SET CACHE CONNECTOR …/ALTER GRAPH <g> REMOVE CACHE, and inspect withSHOW GRAPH CACHES.TTLis in seconds;MAX_BYTESaccepts aK/M/G/Tbinary 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'}), aLIMIT, or a boundedCALL— 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
MATCHpart — and it pipes:Previously this join had to be spelled asMATCH (e:Employee) MATCH (i:Invoice {employee_id: e.id}) RETURN e.name, i.total;WHERE i.employee_id = e.id. Works withoutUSE(each part routes by its own labels) and with multiple keys ({country: o.country, city: o.city}). - SQL Server connector. New
sqlserverconnector 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 inmultimode with an ADO-style connection string:There is no standaloneADD CONNECTOR mssql TYPE sqlserver URI 'Server=localhost,1433;Database=test;User Id=sa;Password=…;TrustServerCertificate=true' MAPPING social;CONNECTOR_TYPE=sqlserverenvironment 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,WITHpipelines (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 newIF NOT EXISTSclause succeeds as a no-op instead of erroring — for re-runnable setup scripts. PlainADD GRAPHstill errors on a duplicate name.- Log levels.
--log-levelnow 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 atDEBUG. See Reference.
MemGQL v0.6.3 - June 21st, 2026
✨ New features & Improvements
- Schema-based routing. In
multimode, queries no longer need aUSE <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; explicitUSEalways wins. Routed federatedJOINandUNIONwork too, and writes route on a unique candidate. Two new statements expose the index:SHOW SCHEMA [FOR <graph>]andREFRESH SCHEMA. Identifier matching is now exact engine-wide. Still gated to explicitUSE: non-default remote databases (Memgraph multi-tenancy), a singleMATCHpattern spanning two backends, and a label-lessMATCH (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-backedicebergconnector and joins other backends in multi-connector federation. Register withADD 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 pnow returns a structured Bolt Node andRETURN ra 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 oneRETURN(RETURN p.name, p) works too. - Connection-less queries (liveness check).
RETURN 1,RETURN 1 + 2 AS xandWITH 1 AS x RETURN xnow evaluate locally with no connector configured and no backend reachable — useful as a Bolt-level health probe. ORDER BYbyRETURNalias 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
NUMERICcolumns now deserialize correctly — whole and fractional values arrive at the Bolt driver as Float andNULLis preserved. PreviouslyNUMERIC-typed columns were unsupported.
MemGQL v0.6.1 - May 31st, 2026
✨ New features & Improvements
- Cross-backend joins (phase 1). Queries that
USEtwo or more different graphs in a single statement now execute as a federated left-deep hash-join chain inside the Bolt server.LinearQuerycarriesparts: Vec<FocusedPart>; a multi-USEquery lowers to aCrossGraphJoinchain of per-partRemoteScans.dispatch_cross_backendpeelsLimit > Sort > Distinct > Project > Filterinto aFederationPipeline, dispatches each part via the per-backendBoltHandler::run_plan, materializes rows to canonical scalars, and folds via hash-join (SQL 3VL —NULLkeys 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/Distinctand 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 macross federation is rejected;CanonicalScalar::Nodemodeling 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/NULLIFgo through the normalFnCallpath). - 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_columnis 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 withoutid_columnon an edge now fails at registration withFailed to parse mapping JSON: missing field 'id_column' at line N, whether the mapping is supplied viaMAPPING_FILEat startup or viaADD MAPPINGat 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 aUNIONover 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 GQLDIFFERENT EDGESdefault — no edge is reused within a single matched path. The recursive CTE carries an_edgesvisited-set whose shape is per-dialect (PostgresARRAY, MySQLJSON_ARRAY, DuckDBLIST). 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. Previouslycount(DISTINCT x)could leak through to backends as the syntheticCOUNT_DISTINCT(...)function (no engine has that). Both Cypher and SQL translators now special-casecount_distinct/collect_distinct/collect_list_distinctto emit the dialect-nativeCOUNT(DISTINCT …).- GQL parse errors now surface the actual ANTLR diagnostic (
line 1:N no viable alternative at input '...') instead of being swallowed into the genericNo statements in GQL querymessage. - 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 bindsxcorrectly (newUnwindClauseAST node; planner emitsLogicalPlan::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.NEXTquery composition resolves names bound on the left-hand-side when the right-hand-sideRETURNreferences them.- Rel-variable reuse across
MATCHclauses parses without a redeclaration error. RETURN 1’s internal_dummyplaceholder 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 MATCHnow works on SQL backends (PostgreSQL, MySQL, DuckDB) — previously Cypher-only.WITHpipeline boundary (GQL scope D) on SQL backends — supportsWITH,WITH DISTINCT,WITH … ORDER BY … LIMIT N, chainedWITH … WITH …, and whole-nodeWITH ncarry-through via derived-table SQL.UNION/UNION ALL/UNION DISTINCTbetween 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 inforeturns a Bolt Map (Memgraph, Neo4j, PostgreSQL, MySQL, DuckDB).collect()aggregate returns a typed Bolt List (Memgraph, Neo4j, PostgreSQL, MySQL, DuckDB).INlist-membership predicate —WHERE n.name IN ['Alice', 'Bob'].STARTS WITH/ENDS WITH/CONTAINSstring predicates portable across all backends.- Quantified path patterns
(){m,n}on SQL backends emit a recursive CTE. MATCH p = (…) RETURN ppath 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 CONNECTORSerror messages now hint at the correct setup statements (ADD MAPPING,ADD CONNECTOR).- Untyped edges
()-[]->(b)on SQL backends translate via aUNION ALLover candidate rel-type mappings.
🐞 Bug fixes
INSERT (a {…}) RETURN a.nameno longer drops theRETURNclause.%(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). RETURNcolumn headers reflect the source expression text (n.age) instead of the literal placeholder"expr".RETURN *no longer leaks internal Strategy-B_u/_eplaceholders.SKIPwithoutLIMITis now honored (was silently dropped).NULLcells are sent as the PackStream0xC0byte (previously the string"NULL").NULLIFandCOALESCEwork end-to-end on every backend.OPTIONAL MATCHWHERE predicates inside the optional pattern land on the correctJOINclause 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 CONNECTIONhandling - Fixed flaky
USE graphbehavior and correctedUSE graphrouting - 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=pinotsingle 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, andUSEstatements - Shortest path queries with
ALL SHORTEST,ANY SHORTEST, andSHORTEST ksupport