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:
| Scheme | Transport |
|---|---|
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.jsonPort 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);
SQL3. 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:latest5. Connect and register the backend
mgconsole --port 7688ADD 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 \
memgqlHANA_DATABASE selects the tenant database of an MDC system.
Dialect notes
- Positional bind parameters use HANA’s native
?form. LIMIT n/SKIP mrender asLIMIT n OFFSET m. HANA nestsOFFSETinside theLIMITclause, so a query with only a skip gets a limit synthesized — a bareOFFSETis a syntax error in HANA.- Booleans use HANA’s native
BOOLEANtype —TRUE/FALSE, not1/0. avg()is cast toDOUBLE; without the cast HANA returns aDECIMAL, which would reach the client as text rather than a number.- Integer division truncates (
5 / 2is2), matching Cypher. DELETEis emitted without a table alias, because HANA’sDELETEgrammar 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 agecomes back asage, notAGE.
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 returnNULLinstead of a map (no error is raised); don’t rely on them on this backend. INSERT … RETURNexecutes the insert but returns the affected-row count instead of the projected values.
For connector configuration, see Reference.