SAP HANA

The SAP HANA connector (type hana; sap-hana, sap_hana, and saphana are accepted aliases) translates GQL queries into HANA SQL. It requires a mapping file that maps graph patterns to relational tables, the same format as every other SQL backend.

The driver is hdbconnect, a pure-Rust implementation of HANA’s SQL Command Network Protocol. No ODBC driver, no JDBC, and no SAP HANA client installation is required at build or runtime.

Targets SAP HANA 2.0 SPS05 and later, SAP HANA Cloud, and SAP HANA Express Edition.

TLS is chosen by the URL scheme

There is no separate TLS setting. The scheme in the connection URL decides:

SchemeTransport
hdbsql://Plaintext. Typical for a local HANA Express
hdbsqls://TLS against the system root store. Required by SAP HANA Cloud

The driver’s own URL options pass straight through, so hdbsqls://host:443?tls_certificate_dir=/certs works for a private CA.

1. Start SAP HANA Express

SAP HANA Express Edition is published for x86_64 only — there is no arm64 image, so it will not start on Apple silicon. On an arm64 machine, point the connector at a remote HANA (SAP HANA Cloud, for example) instead and skip to step 3.

HANA needs roughly 10 GB of memory available to Docker and takes several minutes to finish its first start.

docker network create memgql-net
 
mkdir -p ./hana-mounts
cat > ./hana-mounts/passwords.json <<'JSON'
{"master_password": "HXEHana1"}
JSON
chmod 600 ./hana-mounts/passwords.json
 
docker run -d --rm \
    --name hana-dev \
    --network memgql-net \
    --hostname hana-dev \
    -p 39017:39017 \
    -p 39013:39013 \
    --ulimit nofile=1048576:1048576 \
    --sysctl kernel.shmmax=1073741824 \
    --sysctl kernel.shmmni=524288 \
    --sysctl kernel.shmall=8388608 \
    --memory=10g \
    --mount type=bind,source="$(pwd)/hana-mounts",target=/hana/mounts \
    saplabs/hanaexpress:2.00.076.00.20240701.1 \
    --agree-to-sap-license \
    --passwords-url file:///hana/mounts/passwords.json

Port 39017 is the SQL port of the HXE tenant database; 39013 is the SYSTEMDB. Follow the startup with docker logs -f hana-dev and wait for Startup finished.

2. Seed data

HANA folds unquoted identifiers to UPPERCASE, on both the CREATE TABLE and the reference side. Because they fold the same way, the lower-case names used here and in the mapping resolve to the same tables — just don’t mix quoted and unquoted spellings of one name.

docker exec -i -u hxeadm hana-dev bash -lc \
    'cat > /tmp/seed.sql && hdbsql -n localhost:39017 -u SYSTEM -p HXEHana1 -I /tmp/seed.sql' <<'SQL'
CREATE COLUMN TABLE persons (
    id   INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name NVARCHAR(255) NOT NULL,
    age  INTEGER
);
CREATE COLUMN TABLE companies (
    id   INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name NVARCHAR(255) NOT NULL
);
CREATE COLUMN TABLE knows (
    id      INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    from_id INTEGER NOT NULL REFERENCES persons(id),
    to_id   INTEGER NOT NULL REFERENCES persons(id)
);
CREATE COLUMN TABLE works_at (
    id         INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    person_id  INTEGER NOT NULL REFERENCES persons(id),
    company_id INTEGER NOT NULL REFERENCES companies(id)
);
 
INSERT INTO persons (name, age) VALUES ('Alice', 30);
INSERT INTO persons (name, age) VALUES ('Bob', 25);
INSERT INTO companies (name) VALUES ('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. Write the mapping

Save as mapping.json, the standard mapping format. Declare every property you plan to query: in multi mode the mapping is also the routing schema, and a property that isn’t declared is a routing error, not a passthrough.

{
  "vertices": [
    {
      "label": "Person",
      "mappedTableSource": {
        "connector": "hana",
        "table": "persons",
        "metaFields": {
          "id": "id"
        }
      },
      "attributes": [
        {
          "name": "name"
        },
        {
          "name": "age",
          "type": "Int"
        }
      ]
    },
    {
      "label": "Company",
      "mappedTableSource": {
        "connector": "hana",
        "table": "companies",
        "metaFields": {
          "id": "id"
        }
      },
      "attributes": [
        {
          "name": "name"
        }
      ]
    }
  ],
  "edges": [
    {
      "label": "KNOWS",
      "from": "Person",
      "to": "Person",
      "mappedTableSource": {
        "connector": "hana",
        "table": "knows",
        "metaFields": {
          "id": "id",
          "from": "from_id",
          "to": "to_id"
        }
      }
    },
    {
      "label": "WORKS_AT",
      "from": "Person",
      "to": "Company",
      "mappedTableSource": {
        "connector": "hana",
        "table": "works_at",
        "metaFields": {
          "id": "id",
          "from": "person_id",
          "to": "company_id"
        }
      }
    }
  ]
}

A HANA schema goes in mappedTableSource.schema when the tables are not in the connection user’s own schema. There is no catalog level: the tenant database is chosen at login, not spelled into a table name.

4. Start MemGQL in multi mode

docker run --rm \
    --name memgql \
    --network memgql-net \
    --stop-timeout 2 \
    -p 7688:7688 \
    --env CONNECTOR_TYPE=multi \
    --env BOLT_LISTEN_ADDR=0.0.0.0:7688 \
    -v ./mapping.json:/data/mapping.json \
    memgraph/memgql:latest

5. Connect and register the backend

mgconsole --port 7688
ADD CONNECTOR hana TYPE hana
    URI 'hdbsql://hana-dev:39017' USER 'SYSTEM' PASSWORD 'HXEHana1';
CREATE GRAPH social FROM FILE '/data/mapping.json';
MATCH (n:Person) RETURN n.name LIMIT 5;

USER and PASSWORD can also be written into the URI (hdbsql://SYSTEM:HXEHana1@hana-dev:39017), but keeping them separate is safer: a HANA password often contains @ or :, which would corrupt the host part of a URL.

For a multitenant (MDC) system, add DATABASE '<tenant>' to select the tenant database at login.

For SAP HANA Cloud, the only change is the scheme and the port:

ADD CONNECTOR hana TYPE hana
    URI 'hdbsqls://<instance>.hana.trial-<region>.hanacloud.ondemand.com:443'
    USER 'MEMGQL' PASSWORD '<password>';

6. 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;

Standalone mode

A single HANA backend can also run without multi, configured entirely from the environment:

CONNECTOR_TYPE=hana \
HANA_URL='hdbsql://hana-dev:39017' \
HANA_USER=SYSTEM \
HANA_PASSWORD=HXEHana1 \
MAPPING_FILE=/data/mapping.json \
    memgql

HANA_DATABASE selects the tenant database of an MDC system.

Dialect notes

  • Positional bind parameters use HANA’s native ? form.
  • LIMIT n / SKIP m render as LIMIT n OFFSET m. HANA nests OFFSET inside the LIMIT clause, so a query with only a skip gets a limit synthesized — a bare OFFSET is a syntax error in HANA.
  • Booleans use HANA’s native BOOLEAN type — TRUE / FALSE, not 1 / 0.
  • avg() is cast to DOUBLE; without the cast HANA returns a DECIMAL, which would reach the client as text rather than a number.
  • Integer division truncates (5 / 2 is 2), matching Cypher.
  • DELETE is emitted without a table alias, because HANA’s DELETE grammar does not accept one.
  • No INSERT … RETURNING; an insert returns the affected-row count.
  • Unquoted identifiers fold to UPPERCASE. Result column names are protected from this — projection aliases are emitted quoted, so RETURN n.age AS age comes back as age, not AGE.

Known limitations

  • Variable-length paths: quantified path patterns ((-[:KNOWS]->()){1,3}) are not pushed down to HANA yet and return an actionable error. Use a bounded fixed-hop chain, a Cypher backend, or cache the graph in Memgraph, which answers the pattern from the cache.
  • collect() is not emitted yet; the query fails with an actionable error.
  • Map projections (RETURN n {.a, .b}) currently return NULL instead of a map (no error is raised); don’t rely on them on this backend.
  • INSERT … RETURN executes the insert but returns the affected-row count instead of the projected values.

For connector configuration, see Reference.