> For the complete documentation index, see [llms.txt](https://docs.soda.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.soda.io/reference/data-source-reference-for-soda-core/sap-hana.md).

# SAP HANA

Access configuration details to connect Soda to an SAP HANA data source.

{% hint style="success" %}
SAP HANA support is currently available in [private preview](/reference/documentation-access-and-licensing/release-stages.md#private-preview) for Soda's design partners.

To request access to the SAP HANA data source for your organization, contact <support@soda.io> with your organization ID.
{% endhint %}

Soda verifies data contracts and runs Metric Monitoring against SAP HANA on-premise, SAP HANA Cloud and SAP HANA Express, through one connector. SAP HANA is a **source only**: Soda cannot host a [Diagnostics Warehouse](broken://pages/UZCAHd8ZBgl01336NkZa) on it. See [Diagnostics Warehouse](#diagnostics-warehouse).

## Requirements

* **A self-hosted Soda Runner, or a local Soda Core installation.** A Soda-hosted runner cannot scan SAP HANA, because the SAP client driver is absent from the launcher image and has to be provisioned on infrastructure you control.
* **The SAP `hdbcli` driver, installed by you.** Soda does not ship it. See [SAP HANA client driver](#sap-hana-client-driver).

### SAP HANA client driver

`hdbcli` is SAP's official Python client. It is distributed under the SAP Developer License, which does not permit Soda to redistribute it, so it is neither a dependency nor an optional extra of the `soda-hana` package, and it is not built into the Soda launcher image. You install it yourself, from SAP's own PyPI listing:

{% code overflow="wrap" %}

```bash
pip install 'hdbcli>=2.28.20,<2.29'
```

{% endcode %}

Keep the upper bound. The 2.29 Linux wheels are built for Python 3.10 and later, and SAP publishes no source distribution, so an unbounded install fails outright on Python 3.9.

Soda imports the driver only when a connection is opened, so the package installs and the CLI starts without it. A missing driver surfaces at connect time as an `ImportError` naming the install command above.

{% hint style="info" %}
On a self-hosted Soda Runner the driver is seeded into the launcher pods by an init container rather than baked into an image. See [Vendor-restricted database drivers](broken://pages/nAuMYvhG1bT9hNhRsS3y#vendor-restricted-database-drivers).
{% endhint %}

## Connection configuration reference

{% hint style="warning" %}
Connecting to SAP HANA requires having installed the `soda-hana` package using the [Private PyPi installation flow](/deployment-options/soda-python-libraries.md#private-pypi-installation-flow) available with an Enterprise license, unless you are using a Runner.

Need access to the private PyPI? Please [contact us](mailto:support@soda.io).
{% endhint %}

Install the following package:

{% code overflow="wrap" %}

```bash
pip install "soda-hana" -i "https://${SODA_CLOUD_API_KEY_ID}:${SODA_CLOUD_API_KEY_SECRET}@enterprise.pypi.soda.io/simple"
```

{% endcode %}

* `${SODA_CLOUD_API_KEY_ID}` and `${SODA_CLOUD_API_KEY_SECRET}` are environment variables that you should store with your API Key ID and API Key Secret, respectively.
* For US host users, use `@enterprise.pypi.us.soda.io`\
  For EU host users, use `@enterprise.pypi.soda.io`

{% hint style="info" %}
Learn how to [generate API keys](/reference/soda-apis/generate-api-keys.md).
{% endhint %}

### Data source YAML

**Create the config file:**

```shellscript
soda data-source create -f ds_config.yml
```

The data source configuration YAML should look like the following:

{% code title="ds\_config.yml" %}

```yaml
type: hana
name: my_hana
connection:
  host: hana.example.com
  port: 39015
  user: SODA
  password: ${env.HANA_PASSWORD}
  schema: ANALYTICS          # optional
```

{% endcode %}

Connection property names are camelCase, matching the names the SAP driver uses.

### Connection parameters

| Parameter                | Required    | Default | Description                                                                                           |
| ------------------------ | ----------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `host`                   | Yes         | —       | Fully qualified host name or IP address of the SAP HANA server.                                       |
| `port`                   | Yes         | —       | SQL port. There is no default. See [SQL port](#sql-port).                                             |
| `authentication`         | No          | `basic` | One of `basic`, `x509` or `kerberos`. See [Authentication methods](#authentication-methods).          |
| `user`                   | For `basic` | —       | Database user. Optional for `x509`, where a server can map by user name as well as by certificate DN. |
| `password`               | For `basic` | —       | Database password. Rejected by the other two methods.                                                 |
| `clientCertificate`      | For `x509`  | —       | Client identity, as an inline PEM string or a path to a PEM file.                                     |
| `clientKey`              | No          | —       | Private key PEM, when it is not already concatenated into `clientCertificate`.                        |
| `clientKeyPassword`      | No          | —       | Password for an encrypted private key.                                                                |
| `kerberosPrincipal`      | No          | —       | The principal the runner authenticates as. Selects the credential cache; see [Kerberos](#kerberos).   |
| `kerberosKeytab`         | No          | —       | Path to a keytab on the runner host. The ticket is acquired from it automatically.                    |
| `database`               | No          | —       | Tenant database name on a multitenant (MDC) system.                                                   |
| `schema`                 | No          | —       | Default schema, applied on connect. See [Schemas and datasets](#schemas-and-datasets).                |
| `encrypt`                | No          | `true`  | Encrypt the SQL connection.                                                                           |
| `sslValidateCertificate` | No          | `true`  | Validate the server certificate.                                                                      |
| `sslTrustStore`          | No          | —       | Path to a PEM trust store, as an alternative to the operating system trust store.                     |

### SQL port

`port` has no default, because the correct value depends on how the system was deployed. Any port from 1 to 65535 is accepted. `<NN>` below is the two-digit instance number.

| Deployment                                                            | Port      |
| --------------------------------------------------------------------- | --------- |
| Multitenant (MDC) system, through SYSTEMDB                            | `3<NN>13` |
| Single-container system, or a direct connection to the initial tenant | `3<NN>15` |
| SAP HANA Cloud                                                        | `443`     |

On a multitenant system, connect to SYSTEMDB on `3<NN>13` and name the tenant with `database`; SYSTEMDB's name server routes the session. Tenants beyond the first also listen on their own ports from `3<NN>40` upwards, which you can connect to directly instead.

### Authentication methods

All three methods run over TLS. Only the properties belonging to the selected method are read, and supplying a `password` alongside `x509` or `kerberos` is rejected when the configuration is parsed rather than at connect time.

{% tabs %}
{% tab title="Basic" %}
Username and password. This is the default, and the only method that needs nothing outside the connection configuration.

{% code title="ds\_config.yml" %}

```yaml
type: hana
name: my_hana
connection:
  host: hana.example.com
  port: 39015
  authentication: basic
  user: SODA
  password: ${env.HANA_PASSWORD}
```

{% endcode %}
{% endtab %}

{% tab title="X.509 client certificate" %}
The method to prefer for technical and service users: certificates rotate per service, with no coordinated password change.

`clientCertificate` takes either an inline PEM string or a path to a PEM file, and the certificate must be PEM-encoded in PKCS8 format. Supply the key and the certificate as one identity, either concatenated into `clientCertificate` or with the key in `clientKey`, which Soda prepends for you. A separate `clientKey` is only meaningful with an inline PEM: a file path with a key prepended to it is no longer a path.

Server trust is separate, and comes from `sslTrustStore` or the operating system trust store.

{% code title="ds\_config.yml" %}

```yaml
type: hana
name: my_hana
connection:
  host: hana.example.com
  port: 443
  authentication: x509
  clientCertificate: /etc/soda/hana-identity.pem
  clientKeyPassword: ${env.HANA_CLIENT_KEY_PASSWORD}
  sslTrustStore: /etc/soda/hana-ca.pem
  user: TECH_USER                 # optional
```

{% endcode %}
{% endtab %}

{% tab title="Kerberos" %}
Single sign-on against an on-premise system. SAP HANA Cloud does not offer it.

The SAP client has no Kerberos credential parameter: it authenticates from the GSS credentials of the host it runs on. Give Soda a keytab with `kerberosKeytab` and it takes care of the rest — **you do not need to run `kinit`**, and nothing extra has to be installed on the runner.

{% code title="ds\_config.yml" %}

```yaml
type: hana
name: my_hana
connection:
  host: hana.example.internal
  port: 30015
  authentication: kerberos
  kerberosPrincipal: soda/runner@EXAMPLE.COM
  kerberosKeytab: /etc/soda/soda-runner.keytab
```

{% endcode %}

`kerberosKeytab` is exported as `KRB5_CLIENT_KTNAME`, the client keytab Kerberos acquires initial credentials from, so the ticket is obtained automatically on connect. `kerberosPrincipal` selects the credential cache the ticket is stored in, which keeps two data sources authenticating as different principals from sharing a ticket.

The SAP client offers no way to pin the principal at connect time, so a keytab holding several principals will authenticate as the one Kerberos selects. **Give the runner a single-principal keytab.** See [Kerberos](#kerberos).
{% endtab %}
{% endtabs %}

### TLS

Soda encrypts the connection and validates the server certificate by default, which is what SAP HANA Cloud and a production on-premise system expect.

An SAP HANA Express instance with a self-signed certificate needs one of the two settings relaxed: either give Soda the certificate's CA with `sslTrustStore`, or set `sslValidateCertificate: false`.

{% code title="ds\_config.yml" %}

```yaml
type: hana
name: my_hana_express
connection:
  host: hxehost.example.internal
  port: 39015
  user: SODA
  password: ${env.HANA_PASSWORD}
  sslValidateCertificate: false
```

{% endcode %}

### Connection test

Test the data source connection:

```bash
soda data-source test -ds ds_config.yml
```

***

## Schemas and datasets

SAP HANA datasets are two-segment: `<data source>/<schema>/<dataset>`. The tenant database is fixed by the connection, through `database` or through the port you connect to, and never appears in a qualified name.

Soda Cloud emits a four-segment name for any data source whose connection carries a database. Soda keeps the segments nearest the dataset and logs `HANA dialect: dropping 1 leading prefix segment(s)` rather than rejecting the name.

`schema` sets the default schema on connect, so a contract can name a dataset without qualifying it. A dataset that names its own schema is read from that schema whatever `schema` says.

Dataset discovery excludes SAP's own schemas: `SYS`, and every schema whose name starts with `_SYS`, which covers `_SYS_BIC`, `_SYS_BI`, `_SYS_REPO`, `_SYS_RT` and `_SYS_STATISTICS`. `SYS` is matched exactly, so a user schema named `SYSTEM` or `SYSADMIN_REPORTS` is discovered normally.

## Identifier case

SAP HANA folds an unquoted identifier to upper case, and a database created through SAP tooling is therefore almost always upper case in the catalog. Soda quotes every identifier it emits, so a name is used exactly as the contract writes it and is never folded on your behalf.

Write schema, dataset and column names in contracts with the case the catalog holds — usually upper case. A contract naming `orders` against a table created as `ORDERS` resolves neither in SQL nor in the catalog.

## Required privileges

To verify contracts, the Soda user needs `SELECT` on the datasets under check.

Soda reads dataset and column metadata from `SYS.TABLES`, `SYS.VIEWS`, `SYS.TABLE_COLUMNS` and `SYS.VIEW_COLUMNS`, and primary-key constraints from `SYS.CONSTRAINTS`. Soda creates nothing on an SAP HANA data source and needs no write privilege on it.

***

## Availability in Soda Cloud

Onboard an SAP HANA data source and its datasets the same way you onboard any other, then author and schedule contracts against them. Dataset profiling, Contract Autopilot, data standards, sample data and lineage discovery are all available. Metric Monitoring runs on data-based metrics only; see [Metric Monitoring](#metric-monitoring).

Every SAP HANA data source is scanned by a self-hosted Soda Runner, so the runner picker offers only self-hosted runners and rejects a Soda-hosted one. The SAP client driver is provisioned on infrastructure you control, which a Soda-hosted runner cannot reach.

One part of Soda Cloud is unavailable: SAP HANA cannot host a Diagnostics Warehouse, and a contract verified against it writes its diagnostics to a warehouse hosted elsewhere. See [Diagnostics Warehouse](#diagnostics-warehouse).

***

## Limitations & edge cases

### Diagnostics Warehouse

**SAP HANA cannot host a Diagnostics Warehouse.** It is not offered as a Diagnostics Warehouse target, and a contract verified against SAP HANA writes its diagnostics to a warehouse hosted elsewhere.

Soda enforces this rather than relying on the configuration being right: pointing a Diagnostics Warehouse at SAP HANA fails when the warehouse connection is tested, naming SAP HANA as source-only. It never half-succeeds and leaves partial diagnostics behind.

### Metric Monitoring

Metric Monitoring runs on SAP HANA, on **data-based metrics**. Soda has no metadata provider for SAP HANA, so monitors that read the warehouse's own catalog rather than the data are unavailable: metadata row count, last modification time, and metadata history, including historical backfilling. A backfill still runs on data-based metrics. See [Metadata data sources](/data-observability/metadata-data-sources.md#sap-hana).

Quartile metrics are supported. SAP HANA rejects a percentile and a `DISTINCT` aggregate over the same column in one `SELECT` (error 260), so Soda splits the query, exactly as it does for Redshift.

### Data types

* **There is no time-zone-aware timestamp type.** A value carrying an offset is converted to UTC and written as a plain `TIMESTAMP`, and `TIMESTAMP` and `TIMESTAMP_TZ` are treated as the same type by schema checks. A naive `TIMESTAMP` column is read as it was written, which in an SAP estate is often server-local rather than UTC.
* **`TIME` values are compared to the second.** SAP HANA's `TIME` carries no fractional seconds, so a sub-second component in a contract is truncated.
* **Binary values reach a Diagnostics Warehouse as text.** A `BLOB`, `VARBINARY` or `ST_GEOMETRY` value in a failed-rows sample is stored as its Python representation, such as `b'abc'`, rather than as binary.
* **`DECIMAL` and `NUMERIC` are one type**, as are `NVARCHAR`, `ALPHANUM` and `SHORTTEXT`. A schema check accepts either spelling of a pair.

### Views and schema evolution

SAP HANA has no SQL-DDL materialized views, so Soda creates none. Calculation Views are a modeling artifact rather than a materialized view, and are not treated as one. Tables and views are both discovered and both verifiable.

### Sampling

Row sampling is **percentage only**, implemented with `TABLESAMPLE BERNOULLI`. SAP HANA has no sampling form that takes an absolute row count, so a sampler configured with one is refused rather than silently reinterpreted.

### Custom SQL

SAP HANA rejects a `SELECT` without a `FROM` clause, so Soda appends `FROM SYS.DUMMY` to a statement that has none. This keeps portable SQL such as `rows_tested_query: SELECT 0` runnable.

One shape is not covered: a set-operation chain that also carries a CTE or parentheses, such as `WITH t AS (…) SELECT 1 UNION SELECT 2`. Only one `FROM` is appended, to the end, and the earlier branches fail on the server. Give each branch its own `FROM SYS.DUMMY` when you write SQL of that shape.

### Kerberos

* **No `kinit`.** `kerberosKeytab` is exported as `KRB5_CLIENT_KTNAME`, the client keytab Kerberos acquires initial credentials from, so Soda obtains the ticket itself on connect. Nothing has to be staged on the runner beyond the keytab, and no Kerberos client tooling has to be installed.
* **One keytab per runner.** The environment variables are process-global. Soda sets them only for the duration of a connect and restores them afterwards, but two SAP HANA data sources opening connections concurrently with different keytabs can still interleave, so do not share a runner between them.
* **Give the runner a single-principal keytab.** The SAP client offers no way to pin the principal at connect time, so a keytab holding several will authenticate as the one Kerberos selects. `kerberosPrincipal` selects the credential cache, not the identity in the keytab.
* **On-premise only.** SAP HANA Cloud does not offer Kerberos authentication.

***

## Troubleshoot

<i class="fa-square-xmark">:square-xmark:</i> **Problem:** Testing the connection fails with `The SAP HANA driver 'hdbcli' is not installed`.

<i class="fa-square-check">:square-check:</i> **Solution:** Install the SAP driver yourself with `pip install 'hdbcli>=2.28.20,<2.29'`. Soda is not permitted to redistribute it, so it is in neither the `soda-hana` package nor the launcher image. On a self-hosted Soda Runner, install it with an init container instead; see [Vendor-restricted database drivers](broken://pages/nAuMYvhG1bT9hNhRsS3y#vendor-restricted-database-drivers).

<br>

<i class="fa-square-xmark">:square-xmark:</i> **Problem:** A scan fails immediately with `Field required` for `port`.

<i class="fa-square-check">:square-check:</i> **Solution:** Add `port` to the connection configuration. It has no default, because the correct value depends on the deployment; see [SQL port](#sql-port).

<br>

<i class="fa-square-xmark">:square-xmark:</i> **Problem:** The connection fails on an SAP HANA Express instance with a certificate validation error.

<i class="fa-square-check">:square-check:</i> **Solution:** SAP HANA Express presents a self-signed certificate. Supply its CA with `sslTrustStore`, or set `sslValidateCertificate: false`.

<br>

<i class="fa-square-xmark">:square-xmark:</i> **Problem:** A contract reports every dataset as missing, although the schema is populated.

<i class="fa-square-check">:square-check:</i> **Solution:** Check the case of the names in the contract. SAP HANA stores unquoted identifiers folded to upper case, and Soda quotes every identifier rather than folding it; see [Identifier case](#identifier-case).

<br>

***

{% hint style="info" %}
You are **not logged in to Soda** and are viewing the default public documentation. Learn more about [Licensing & documentation access](/reference/documentation-access-and-licensing.md).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.soda.io/reference/data-source-reference-for-soda-core/sap-hana.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
