Skip to content

How Custom JDBC Drivers Work

  • Self-hosted

This page follows a custom driver through its whole life: from the moment the Dataplane starts and discovers the definition, through validation and registration, to the connector appearing in the app and assembling real JDBC URLs at connection time.

From Startup to a Working Connector

Qualytics scans for custom drivers exactly once, when the Dataplane starts. There is no hot reload: adding, replacing, or removing a custom driver requires a Dataplane restart to take effect.

flowchart TD
    A[Dataplane starts] --> B["Scan the classpath for<br/>META-INF/jdbc-drivers/*.yaml"]
    B --> C{Every definition<br/>valid?}
    C -- "No" --> D["Dataplane refuses to start.<br/>The log names each offending<br/>file, key, and reason"]
    C -- "Yes" --> E{Prefix conflicts?}
    E -- "Overrides a built-in or<br/>duplicates another custom" --> D
    E -- "No" --> F[Custom drivers merge with<br/>the built-in driver catalog]
    F --> G[Driver metadata is sent<br/>to the platform]
    G --> H[Connector appears in the<br/>add-datastore picker]
    H --> I[Connection form renders the<br/>fields the YAML declares]

Three properties of this pipeline are worth calling out:

  • Validation is all-or-nothing. One misconfigured definition stops the whole Dataplane rather than silently dropping that driver. This sounds harsh, but it means a typo can never quietly remove a connector your team depends on: the failure is loud, immediate, and named in the startup log.
  • Built-in connectors are protected. A custom definition whose prefix matches a built-in connector (or that ships a YAML file with a built-in's name) is rejected at startup. A custom driver can add databases; it can never change how PostgreSQL, Snowflake, or any other built-in behaves.
  • The registry is the single source of truth. The same definition drives everything downstream: the picker entry, the connection form, URL assembly, and SQL generation. There is no second place to configure any of it.

What the Connection Form Sends

The connection form renders the fields declared under connectionSpec.fields, with their labels, types, defaults, and dependency rules. When someone saves the form, the values travel to the driver through two channels:

  • The four canonical inputs: fields named exactly username, password, database, and schema are first-class connection values.
  • The parameters map: every other field (host, port, tokens, regions, and anything else the driver needs) rides in a key-value map.

This split explains the parameters.<name> notation you will see in driver definitions: a property mapping such as access_token: parameters.access_token reads the form field named access_token out of that map. The four canonical inputs are referenced by their bare names.

How the JDBC URL Is Assembled

The Dataplane builds the URL it connects with. At connection time it resolves the YAML's URL template against the submitted form values:

  1. Placeholders resolve to form fields. Each {placeholder} in the template is filled from the field with the same name, falling back to the field's defaultValue when the form left it empty.
  2. Static parameters always apply. Every entry under url.staticParams is appended to the URL.
  3. Conditional parameters apply when their field has a value. An entry under url.conditionalParams renders only when its key field resolved to a non-empty value, so an empty optional Schema field adds nothing.
  4. An authentication variant can rewrite the URL. When the form's authentication_type matches an entry under url.authVariants, that variant's urlTemplate replaces the base template, and its parameters and property mappings are applied on top. This lets one driver offer, for example, both password and token sign-in with different URL shapes.
  5. The parameter separator is declared, never guessed. By default, parameters join query-style (? for the first, & after). Databases whose URLs join parameters with ; or , must declare it in url.paramSeparator; a template that contains ; together with parameters and no declaration is a validation error.

For a complete definition that exercises each of these rules, with the exact URL each combination of inputs produces, see the Requirements example.

Testing a Connection

When someone tests a connection built on a custom driver, Qualytics runs the probe statement from the definition's config.connectionTest, or SELECT 1 when the definition does not set one. Databases that cannot run a bare SELECT 1 (for example, ones that require a FROM clause) declare their own probe.

SQL Dialects

Most databases work with the stock SQL handling, and most definitions do not declare a dialect. When a database's identifier quoting or type mapping diverges enough to need one, the definition's dialectClass points at a dialect class shipped inside the JAR, and the Dataplane registers it at startup. A dialect class that cannot be loaded is treated like any other validation failure: the Dataplane refuses to start and names the reason. The class contract lives on the Requirements page.

Enrichment Support

A custom connector can also support enrichment datastores. The definition's connectionSpec.supportsEnrichment defaults to true; set it to false for engines that are read-only or query-only and cannot store the enrichment outputs Qualytics writes.

Life Across Upgrades

The driver lives inside your Dataplane image, so it follows the image, not the platform version. When an upgrade rebuilds the image without the driver JAR, the connector disappears from the picker, and existing datastores of that type can no longer connect, because the registry refuses a prefix it does not know. Carry the driver forward in every image rebuild. The Install how-to and the FAQ cover this in practice.