# JDBC sources

## Introduction

A source can front **any database that ships a JDBC driver**: NetSuite,
Snowflake, Databricks, BigQuery, SQL Server, Oracle, Trino, and the thirty-odd
others in the preset list below. You bring the driver jar; swarmvia loads it in
an isolated Java runtime that belongs to your organization alone, dials the
database with a login that never leaves swarmvia, and serves the result over
the same endpoint every database source has.

To a caller a JDBC source is a [database source](/docs/sources/database): the
same `POST /` query body, the same `/structure`, the same job handles,
the same MCP tools, the same concurrency limit and access log. Only the leg
after admission differs, and nothing about it is specific to a vendor. Presets
are data. A vendor swarmvia has never heard of works with the generic preset.

Use the native **Postgres** and **MySQL** backends for those engines. A JDBC
source exists for everything else, and for the rare case where a driver feature
matters more than the lighter native path.

## The endpoints

```
POST  https://{source}.swarmvia.cloud
GET   https://{source}.swarmvia.cloud/structure
GET   https://{source}.swarmvia.cloud/__nth/jobs/{jobId}
```

Identical to a database source: `X-Swarmvia-Key`, `queries` with bound
parameters, `maxRows`, `waitMs`, `rowFormat`. Two differences a caller sees:

- **Placeholders are `?`**, never `$1`. JDBC drivers take positional `?`
  markers and every vendor agrees on that.
- **Every cell is text.** The driver's `getString()` of each value is returned
  as the JSON string it printed, or `null`. Column types are the driver's own
  names (`NUMBER`, `varchar`, `TIMESTAMP_NTZ`). Nothing is cast on the way out,
  because the vendor's text form is the one its users recognize.

## Drivers

A driver is the jar a source loads. There are two ways to set it, both on the
source:

- **Maven coordinates** (`group:artifact:version`) for drivers published on
  Maven Central, which most are. Vendor templates fill these in. swarmvia
  fetches the jar when you save, verifies its SHA-1 against Central, and stores
  it, so a query never depends on Central being up. Sources with the same
  coordinates share one stored jar.
- **Upload** the jar for drivers a vendor only hands out from its own site
  (NetSuite, BigQuery's Simba driver, Dremio, Sybase, Netezza, Denodo).

If a vendor's download page asks for an operating system, choose **Linux
64-bit** (x86-64, glibc). Every source runs there, on Java 21, so take a
build for Java 8 through 21; a build for a newer Java will not load. Take the
JDBC download, not ODBC.

Every jar is inspected before it is accepted: it must be a real jar and declare
at least one `java.sql.Driver` in `META-INF/services`. Up to 150 MB per jar and
1 GB per organization.

To upgrade one source, change its coordinates. To upgrade every source on a
driver, use **Change version** (or **Replace**, for an upload) under **Drivers**
in the sidebar. Each source picks the new jar up on its next connection; the old version is removed once every source has been told about
the new one. A driver still used by a source cannot be deleted; point those
sources elsewhere first.

The jar runs only in your organization's runtime, with your sources' logins,
which it receives per request. It never shares a process with another
customer's code and never sees another organization's secret.

## Presets

A preset fills the form: the driver to fetch, the URL template, the property
rows the driver expects, and where an access token goes. Pick one under
**Sources → New source → JDBC data source**, then fill the placeholders. The
JDBC URL and the public properties stay editable, so a preset is a head start,
not a cage.

| Preset | Driver | Auth |
|---|---|---|
| [NetSuite SuiteAnalytics Connect](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_N751293.html) | upload from the vendor | password, OAuth 2.0 |
| [Snowflake](https://docs.snowflake.com/en/developer-guide/jdbc/jdbc) | `net.snowflake:snowflake-jdbc` (Maven Central) | password, key or service-account file, OAuth 2.0 |
| [Databricks SQL](https://docs.databricks.com/en/integrations/jdbc/index.html) | `com.databricks:databricks-jdbc` (Maven Central) | token or key, OAuth 2.0 |
| [Google BigQuery](https://cloud.google.com/bigquery/docs/reference/odbc-jdbc-drivers) | upload from the vendor | key or service-account file |
| [Google Cloud Spanner](https://cloud.google.com/spanner/docs/jdbc-drivers) | `com.google.cloud:google-cloud-spanner-jdbc` (Maven Central) | key or service-account file |
| [Amazon Redshift](https://docs.aws.amazon.com/redshift/latest/mgmt/jdbc20-install.html) | `com.amazon.redshift:redshift-jdbc42` (Maven Central) | password |
| [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/jdbc-v3-driver.html) | `com.amazonaws.athena:athena-jdbc` (Maven Central) | password |
| [Microsoft SQL Server / Azure SQL / Synapse](https://learn.microsoft.com/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server) | `com.microsoft.sqlserver:mssql-jdbc` (Maven Central) | password, OAuth 2.0 |
| [Oracle Database / Autonomous Database](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/) | `com.oracle.database.jdbc:ojdbc11` (Maven Central) | password, OAuth 2.0 |
| [IBM Db2](https://www.ibm.com/docs/en/db2/11.5?topic=applications-jdbc) | `com.ibm.db2:jcc` (Maven Central) | password |
| [IBM Informix](https://www.ibm.com/docs/en/informix-servers/14.10?topic=programming-informix-jdbc-driver) | `com.ibm.informix:jdbc` (Maven Central) | password |
| [SAP HANA](https://help.sap.com/docs/SAP_HANA_CLIENT/f1b440ded6144a54ada97ff95dac7adf/434e2962074540e18c802fd478de86d6.html) | `com.sap.cloud.db.jdbc:ngdbc` (Maven Central) | password, OAuth 2.0 |
| [Teradata](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/frameset.html) | `com.teradata.jdbc:terajdbc` (Maven Central) | password |
| [Trino / Starburst](https://trino.io/docs/current/client/jdbc.html) | `io.trino:trino-jdbc` (Maven Central) | password, OAuth 2.0 |
| [Presto](https://prestodb.io/docs/current/installation/jdbc.html) | `com.facebook.presto:presto-jdbc` (Maven Central) | password |
| [ClickHouse](https://clickhouse.com/docs/en/integrations/java#jdbc-driver) | `com.clickhouse:clickhouse-jdbc` (Maven Central) | password, OAuth 2.0 |
| [SingleStore](https://docs.singlestore.com/cloud/developer-resources/connect-with-application-development-tools/connect-with-java-jdbc/the-singlestore-jdbc-driver/) | `com.singlestore:singlestore-jdbc-client` (Maven Central) | password |
| [Vertica](https://docs.vertica.com/latest/en/connecting-to/client-libraries/accessing/java/) | `com.vertica.jdbc:vertica-jdbc` (Maven Central) | password, OAuth 2.0 |
| [Exasol](https://docs.exasol.com/db/latest/connect_exasol/drivers/jdbc.htm) | `com.exasol:exasol-jdbc` (Maven Central) | password, OAuth 2.0 |
| [Firebolt](https://docs.firebolt.io/Guides/developing-with-firebolt/connecting-with-jdbc.html) | `io.firebolt:firebolt-jdbc` (Maven Central) | token or key |
| [MotherDuck](https://motherduck.com/docs/integrations/language-apis-and-drivers/jdbc-driver/) | `org.duckdb:duckdb_jdbc` (Maven Central) | token or key |
| [Dremio](https://docs.dremio.com/current/client-applications/drivers/jdbc/) | upload from the vendor | password, token or key |
| [Apache Hive / Spark Thrift](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients#HiveServer2Clients-JDBC) | `org.apache.hive:hive-jdbc` (Maven Central) | password |
| [Apache Druid](https://druid.apache.org/docs/latest/api-reference/sql-jdbc/) | `org.apache.calcite.avatica:avatica-core` (Maven Central) | password |
| [Apache Phoenix](https://phoenix.apache.org/server.html) | `org.apache.phoenix:phoenix-queryserver-client` (Maven Central) | password |
| [CrateDB](https://cratedb.com/docs/jdbc/) | `io.crate:crate-jdbc` (Maven Central) | password |
| [Elasticsearch SQL](https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-jdbc.html) | upload from the vendor | password, token or key |
| [OpenSearch SQL](https://github.com/opensearch-project/sql-jdbc) | `org.opensearch.driver:opensearch-sql-jdbc` (Maven Central) | password, token or key |
| [Sybase / SAP ASE](https://help.sap.com/docs/SAP_ASE/a8e5e7d7c6a8438bae55f6b31f36a4a0) | upload from the vendor | password |
| [IBM Netezza](https://www.ibm.com/docs/en/netezza?topic=jdbc-driver) | upload from the vendor | password |
| [Denodo](https://community.denodo.com/docs/html/browse/latest/en/vdp/developer/access_through_jdbc/access_through_jdbc) | upload from the vendor | password |
| [PostgreSQL-compatible via JDBC](https://jdbc.postgresql.org/documentation/use/) | `org.postgresql:postgresql` (Maven Central) | password, OAuth 2.0 |
| [MySQL-compatible via JDBC](https://dev.mysql.com/doc/connector-j/en/) | `com.mysql:mysql-connector-j` (Maven Central) | password, OAuth 2.0 |

**Other (generic JDBC)** takes any driver, any `jdbc:` URL, and any property
bag. It is how a vendor not listed here is connected, and the list above is
nothing more than this form with the fields filled in.

## Properties and secrets

A JDBC driver is configured by a flat bag of named strings. swarmvia splits
that bag by who may see it:

- **Public properties** live on the source: account, warehouse, role,
  application name, TLS mode. They appear in the dashboard and in the source's
  settings.
- **Secret properties** live in the credential: password, token, client
  secret, passphrase. They are written once and never shown back.
- **Files** live in the credential too: a private key, a service-account JSON,
  a wallet. Up to eight, 64 KB each. Reference one as `${file:name}` in a
  property value or in the URL; it is written to a private directory for the
  connection and removed when the connection ends. `${files}` is that
  directory, for a driver that wants one (an Oracle wallet location,
  `TNS_ADMIN`).

The secret bag wins. A name set in both places takes the credential's value,
so a value typed into the source form can never override a secret, and a JDBC
URL is refused if it carries a property the credential marks secret. Secrets
go in the credential; the URL is shown and logged.

A JDBC login is one credential, reusable across sources like any other.
Rotating it walks every source on it back to *needs connectivity* until **Test
connection** passes again.

## OAuth2 access tokens

Drivers that take a short-lived access token (Snowflake, Databricks, Azure SQL
and Synapse, Oracle, Trino, Vertica, Exasol, HANA, ClickHouse) get one from
swarmvia. Give the credential an OAuth 2.0 client-credentials grant
and name where the driver wants the token: a **property** (`token`,
`Auth_AccessToken`, `accessToken`, `OAuth2Token`) or a **file** (Oracle reads
one from `oracle.jdbc.tokenLocation`).

swarmvia mints the token at the identity provider, caches it until shortly
before it expires, and hands it to the driver on every connection. The driver
never refreshes anything itself, which matters because every query is a fresh
connection and a driver left to its own devices would hit your identity
provider per query. Sources that share a credential share the token.

A rejected client is answered the way an HTTP source answers one: `503` with
the identity provider's error, a reconnect link, and no secret in the body.

## Limits and sizing

Query limits are the database-source limits:

| Setting | Default | Ceiling |
|---|---|---|
| Statement timeout | 30s | 10m |
| Max rows | 10,000 | 50,000 |
| Max result bytes | 8 MiB | 16 MiB |
| Result TTL | 1h | 24h |

Two things are JDBC's own:

**Runtime memory.** A JDBC source runs in a Java runtime with **1 GB**, which
fits a JVM, a driver, and sixteen concurrent queries at the 8 MiB result
ceiling. A driver that buffers wide results can still exhaust it; lower
`maxRows` first, and if queries against the source keep failing, contact
[support@swarmvia.com](mailto:support@swarmvia.com).

**When no runtime is available.** The runtimes come from a shared pool of
instances. When none can be started, a source either **fails** the query with
`503 dialer_at_capacity` and a `Retry-After` the caller should honour, or
**queues** it as a job to run when an instance frees up. Queueing needs Async
on. Choose under **Concurrency**; the default is to fail, because a hidden
queue is the one thing an operator cannot budget for.

Plan limits on JDBC sources, separate from the source count:

| Plan | JDBC sources |
|---|---|
| Free | 0 |
| Starter | 1 |
| Team | 3 |
| Business | 10 |

A scheduled job against a JDBC source keeps a runtime awake for as long as it
runs; the per-plan cap is what keeps a dozen hourly crons from becoming a bill
nobody meant to run.

## Read-only, and what that means here

swarmvia asks the driver for a read-only connection and runs each statement in
a transaction it rolls back. What that is worth depends entirely on the driver:
some enforce it, some record it as a hint, some ignore it. **Test connection**
reports what the driver said (`readOnly: true` or `false`), and the honest
statement is this: **the login's grants are the whole boundary.** Connect as a
role that can read what it should and nothing else, the same discipline a
Postgres source asks for, and treat a read-only connection as a convenience,
not a control.

## Test connection

**Sources → Settings → Test connection** opens a real connection through the
driver and reports which stage failed:

| Reason | Meaning |
|---|---|
| `driver_not_found` | The driver jar could not be fetched. Check the driver under JDBC drivers. |
| `driver_load_failed` | The jar was fetched but failed to load, or its checksum did not match. Replace it. |
| `driver_class_missing` | The jar declares no `java.sql.Driver`. It is not a JDBC driver, or not the right jar from the vendor's bundle. |
| `not_jdbc` | No loaded driver accepts this URL's `jdbc:` subprotocol. The URL and the driver disagree. |
| `unreachable`, `tls_refused`, `tls_verify_failed` | The network and TLS stages, as for any database source. |
| `auth_failed`, `database_missing`, `insufficient_privilege` | The login, the database name, or the grants. |

On success it records the product the driver reported (`NetSuite`, `Snowflake`,
`Microsoft SQL Server`) and the driver's own identifier quoting, which the MCP
`source_count_rows` tool uses to quote names for an engine it has never seen.

## Errors

Failures carry the driver's `SQLSTATE` where the driver provides one, in the
same `db_error` shape as every database source. Statement timeouts and
cancellations are `57014`. A failure before any statement ran (a driver that
would not load, a connection that would not open) is a `502` or `503` naming
the stage, never a `400`: it is the source's configuration, not the caller's
statement.

Cancelled queries are cancelled **in the driver**, not just dropped, so a
runaway statement stops at the database when the caller gives up.

## From an agent

The same tools as any database source: `source_schema`, `source_query`,
`get_query_result`, `source_count_rows`. `source_schema` reads the driver's
own catalog (`DatabaseMetaData`), so engines without `information_schema`
work. A JDBC source lists in `list_sources` as `backend: jdbc · <product>`.

## What this is not

**Not ODBC.** ODBC drivers are native code for a specific operating system and
architecture, and every vendor's build is different; there is no way to load one
safely in a shared runtime. Every vendor that ships ODBC also ships JDBC, and
the JDBC driver is the portable one. If a system only has an ODBC driver, it
will not connect here.

**Not a driver host for your application.** The runtime opens one connection
per query and sleeps when idle. It is for agents, analysts and scheduled
analytical work, like every other database source.

**Not Kerberos.** Drivers that can only authenticate with a Kerberos ticket
have no keytab here. Use the vendor's password, token or OAuth2 path.
