PostgreSQL

The PostgreSQL connector (CONNECTOR_TYPE=postgres) translates GQL queries into SQL and executes them against PostgreSQL. It requires a mapping file that maps graph patterns to relational tables.

1. Start PostgreSQL

docker network create memgql-net
 
docker run -d --rm \
    --name postgres-dev \
    --network memgql-net \
    -p 5432:5432 \
    --env POSTGRES_PASSWORD=postgres \
    postgres:18

2. Seed data

docker exec -i postgres-dev psql -U postgres << 'SQL'
CREATE TABLE IF NOT EXISTS persons (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT
);
CREATE TABLE IF NOT EXISTS companies (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS knows (
    from_id INT REFERENCES persons(id),
    to_id INT REFERENCES persons(id)
);
CREATE TABLE IF NOT EXISTS works_at (
    person_id INT REFERENCES persons(id),
    company_id INT REFERENCES companies(id)
);
 
INSERT INTO persons (id, name, age) VALUES (1, 'Alice', 30), (2, 'Bob', 25);
INSERT INTO companies (id, name) VALUES (1, 'Acme Corp');
INSERT INTO knows (from_id, to_id) VALUES (1, 2);
INSERT INTO works_at (person_id, company_id) VALUES (1, 1);
SQL

3. Start MemGQL

docker run --rm \
    --name memgql \
    --network memgql-net \
    --stop-timeout 2 \
    -p 7688:7688 \
    --env CONNECTOR_TYPE=postgres \
    --env POSTGRES_URL="host=postgres-dev user=postgres password=postgres dbname=postgres" \
    --env MAPPING_FILE=/data/mapping.json \
    --env BOLT_LISTEN_ADDR=0.0.0.0:7688 \
    -v ./mapping.json:/data/mapping.json \
    memgraph/memgql:latest

4. Connect

mgconsole --port 7688

5. Query

MATCH (p:Person) RETURN p.name, p.age;
MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p.name, c.name;
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name;

For environment variables, see Reference.

TLS connections

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

{
  "name": "pg",
  "type": "postgres",
  "connection": {
    "uri": "postgresql://user:pass@db.example.com:5432/app?sslmode=require",
    "sslRootCert": "/etc/ssl/certs/customer-ca.pem",
    "trustServerCertificate": false
  }
}
FieldDescription
sslmode= (in uri)libpq semantics, including the default: disable never attempts TLS, prefer uses it when it works, require makes it mandatory.
sslRootCertPath to a PEM bundle of CA certificates to trust instead of the system roots. Omit it to use the system trust store.
trustServerCertificateEncrypt without verifying the server’s identity.

TLS is implemented with rustls, so there is no OpenSSL or other C library to install.

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

Two more guardrails:

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

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

JSONB columns

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

"attributes": [
  { "name": "voltage", "column": "props", "path": "electrical.voltage", "type": "Double" },
  { "name": "props", "type": "Json" }
]
MATCH (c:Component) WHERE c.voltage > 100 RETURN c.sku, c.props.rohs;

Both forms push the extraction down to PostgreSQL, and a declared type makes the comparison numeric instead of lexicographic. See Schema File → JSON / JSONB columns.

Supported GQL features

FeaturePostgres
MATCH / WHERE / RETURN
Pattern-level WHERE (MATCH (n WHERE …))
Multiple MATCH clauses in one query
OPTIONAL MATCH
WITH clause (chain query steps)
WITH DISTINCT / WITH … ORDER BY … LIMIT N
Multiple chained WITH steps
Pass a whole node through WITH n
Typed edge (a)-[r:R]->(b)
Untyped edge ()-[]->(b)
UNION / UNION ALL / UNION DISTINCT
Quantified path (){m,n} (bounded)
Whole-node RETURN n / whole-rel RETURN r
Map projections RETURN n {.a, .b}
IN list membership WHERE x IN [...]
STARTS WITH / ENDS WITH / CONTAINS
collect() aggregate
count, sum, avg, min, max
COUNT(DISTINCT …)
Arithmetic + - * / %
CASE WHEN … THEN … ELSE … END
COALESCE, NULLIF
INSERT (a {…}) RETURN a.x
DELETE
SET / REMOVE (property update / delete)
DETACH DELETE
INTERSECT / EXCEPT
Quantified path (){m,} (unbounded)
Shortest-path (ALL SHORTEST / SHORTEST k)

Known limitations

  • Unbounded variable-length paths (()-[*]->()) are rejected. Bound the depth ({1,5}) or run the query against a Cypher backend.
  • FOR x IN [...] (UNWIND-style) is rejected with an actionable error. Cypher backends (Memgraph, Neo4j) handle this syntax natively.
  • Path variables on variable-length patterns: MATCH p = (a){1,3}(b) RETURN p is not supported. Drop the p = binding (or query a Cypher backend) and RETURN the individual nodes / edges instead.