# 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](https://memgraph.com/docs/memgraph-zero/memgql/reference#mapping-schema) that maps graph
patterns to relational tables, the same format as every other SQL backend.

The driver is [hdbconnect](https://crates.io/crates/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.

```bash
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.

```bash
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](https://memgraph.com/docs/memgraph-zero/memgql/reference#mapping-schema). 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.

```json
{
  "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

```bash
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

```bash
mgconsole --port 7688
```

```gql
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:

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

## 6. Query

```gql
MATCH (p:Person) RETURN p.name, p.age;
```

```gql
MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p.name, c.name;
```

```gql
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:

```bash
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](https://memgraph.com/docs/memgraph-zero/memgql/multiple-graphs#caching-a-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](https://memgraph.com/docs/memgraph-zero/memgql/reference#sap-hana-hana).
