§ reference

Connecting to the database

Every entry of a shard member’s configs map (primary-config, secondary-config, jobs-config, lock-config, or any name you add) says how Ekbatan connects to one database. The application’s connection pools and Flyway migrations connect the same way, from the same entry.

The fields

FieldRequiredWhat it is
jdbc-urlyesThe JDBC URL. It may not carry a user name or a secret; see No user name or secret in the URL.
usernamenoThe database user. Leave it out only when something else supplies the user, such as a driver’s login plugin. An empty value is refused.
passwordnoThe login password. Leave it out for a login that has none: an IAM token, a client certificate, Kerberos, or a driver plugin that supplies it. The empty string stays a valid password.
driver-class-namenoThe JDBC driver class, when it should not be found from the URL. A blank value means none, as when it is left out.
data-source-propertiesnoExtra settings for the driver; see below.
maximum-pool-size, minimum-idle, idle-timeout, leak-detection-thresholdnoPool settings.

With no username, and nothing else supplying one, the PostgreSQL, MySQL and MariaDB drivers all log in as the operating-system user. Ekbatan therefore logs a warning when a pool or a migration is built without a username.

data-source-properties

Extra settings handed to the JDBC driver - the same settings you could put in the URL’s query string as ?name=value, given as a map instead. They are never added to the URL: Ekbatan passes them to the driver beside it, the JDBC standard way - Driver.connect(url, properties), with user and password in the same properties - for every connection a pool or a migration opens. The driver merges them with the URL’s own settings, so the PostgreSQL, MySQL and MariaDB drivers each read a setting the same way from either place; kept out of the URL, they never appear where a URL is logged, and need no percent-encoding.

primary-config:
  jdbc-url: jdbc:postgresql://orders-db.internal:5432/orders?sslmode=verify-full&sslcert=/run/secrets/db/client.crt&sslkey=/run/secrets/db/client.pk8
  username: orders_app            # no password: the client certificate is the login
  data-source-properties:
    sslpassword: ${DB_KEY_PASSWORD}
    ApplicationName: orders-service

The rules:

The same settings in each configuration format:

# application.yml - Spring Boot, Micronaut
ekbatan:
  sharding:
    groups:
      - members:
          - configs:
              primary-config:
                data-source-properties:
                  ApplicationName: orders-service
                  options: "-c statement_timeout=30000"
# application.properties - Quarkus, or any properties file
ekbatan.sharding.groups[0].members[0].configs.primary-config.data-source-properties.ApplicationName=orders-service
ekbatan.sharding.groups[0].members[0].configs.primary-config.data-source-properties.options=-c statement_timeout=30000
// plain Java
dataSourceConfig()
        .jdbcUrl("jdbc:postgresql://orders-db.internal:5432/orders")
        .username("orders_app")
        .dataSourceProperties(Map.of("ApplicationName", "orders-service"))
        .build();

No user name or secret in the URL

A URL is logged and repeated - Flyway logs it at startup, drivers repeat it in their errors - so a URL that carries a user name or a secret is refused when the configuration is built. The error names every such setting, each once, and where each goes, and never repeats the URL or a value:

In the URLGoes to
the login user: user, or a name before an @ (jdbc:mysql://app@db/orders)username
the login password: password, or app:password@password
any other user name or secret: a key or keystore password, an identity provider’s, proxy’s or SSH tunnel’s user and password, a client secret, a tokendata-source-properties

The check reads the URL’s own text - the text that gets logged - and not what its driver makes of it: asked what a URL carries, PostgreSQL’s driver adds the password from .pgpass and a service file’s user and password, and MySQL’s driver answers nothing at all for a URL with two hosts. It reads a name only where an accepted driver reads a setting’s name: in the query, split at & (?a=1&b=2); inside the parentheses of MySQL’s host forms ((host=db1,user=a), address=(host=db1)(user=a)), each host on its own; in the ; settings of DataDirect’s and CData’s drivers; and in any URL the URL holds - an SSH tunnel’s or a proxy’s real URL - read the same way. A word anywhere else is not a setting: a host or a database named password, or a value such as ApplicationName=svc,user=batch, is accepted. A setting with no value (?password=) carries nothing and is accepted too. Names are matched ignoring case and spaces, as MySQL’s and MariaDB’s drivers read them; values are never read.

Which names hold a user name or a secret is decided once, in a ledger of every setting of every driver whose URL Ekbatan accepts - driver-settings.tsv, each setting marked from the driver’s own source code or, for a closed-source driver, its vendor’s documentation. Besides user and password, these are refused:

DriverAlso refused
PostgreSQL (pgjdbc)sslpassword
MySQL Connector/Jpassword1, password2, password3, clientCertificateKeyStorePassword, trustCertificateKeyStorePassword, xdevapi.ssl-keystore-password, xdevapi.ssl-truststore-password (and their xdevapiSsl...Password aliases)
MariaDB Connector/JkeyStorePassword, trustStorePassword, keyPassword (and their ...CertificateKeyStorePassword aliases), secretKey (its AWS-IAM login plugin), password2, password3, … (PAM)
AWS Advanced JDBC WrapperdbUser, idpUsername, idpPassword, cacheUsername, cachePassword, cp-username, cp-user, cp-password, cp-overrideDefaultUser, cp-overrideDefaultPassword, and any refused name behind monitoring-, topology-monitoring-, blue-green-monitoring-, limitless-router-monitor- or frt-, which pass a setting on to its monitoring connections
Azure identity extensionsazure.username, azure.password, azure.clientSecret, azure.clientCertificatePassword
pgjdbc-ng (jdbc:pgsql:)ssl.key.password (sslPassword), ClientUser
CData (PostgreSQL, MySQL, MariaDB)SSHUser, SSHPassword, SSHClientCert, SSHClientCertPassword, FirewallUser, FirewallPassword, ProxyUser, ProxyPassword, KerberosUser, IntegratedUser, SSLClientCert, SSLClientCertPassword, CertificatePassword, OAuthClientSecret, OAuthAccessToken, OAuthRefreshToken, OAuthVerifier, OAuthJWTCert, OAuthJWTCertPassword, OAuthEncryptKey, MFAToken, AWSSecretKey, RTK
DataDirect (PostgreSQL, MySQL)ProxyUser, ProxyPassword, ClientUser, KeyStorePassword, KeyPassword, TrustStorePassword, SecretKey
Kingbase’s pgjdbc forksukpwdpassword, usbkeypin
SSH tunnel drivers (jdbc-ssh, jdbc-ssh-tunnel, jdbc-sshj)jdbc.ssh.username, jdbc.ssh.password, jdbc.ssh.passphrase, sshUser, sshPassphrase, username, private.key.password, and jdbc-sshj’s SSH password and a user before its SSH host’s @, refused as the login’s - see the jdbc-sshj note below
MySQL Connector/MXJserver.initialize-user.user, server.initialize-user.password
Google Cloud SQL and AlloyDB connectors, AWS and Aliyun Secrets Manager JDBC, Aurora DSQL connector, Drizzle, Testcontainers, OpenTelemetry, OpenTracing, P6Spy, Sentry, log4jdbc, jdbcdslog, JAMon, Druid’s wrap-jdbc, Proxool, JavaMelody, Delinea, CyberArk ASCP, Broadcom DevTest, junixsocket, Brave, the openGauss, Hologres, PolarDB and TiDB forksnothing more

Ways to log in

LoginHowStatus
A password from a secret storepassword: ${DB_PASSWORD}, filled from a Kubernetes Secret, a mounted file, or a store read at startuptested
Client certificate, key without a passwordsslcert / sslkey / sslrootcert in the URL, no passwordconfiguration accepted
Client certificate, encrypted key (PostgreSQL)as above, plus sslpassword in data-source-propertiestested end to end
Client certificate, keystore (MySQL, MariaDB)keystore in the URL, clientCertificateKeyStorePassword (MySQL) or keyStorePassword (MariaDB) in data-source-propertiesconfiguration accepted
A driver plugin supplies user and password (MariaDB)credentialType in data-source-properties, no username or passwordtested end to end
AWS RDS IAMjdbc:aws-wrapper:postgresql://...?wrapperPlugins=iam, or MariaDB’s credentialType=AWS-IAM (driver 4 needs its mariadb-java-client-aws jar for it); no passwordconfiguration accepted; not run against AWS
AWS Secrets Manager, rotatingjdbc-secretsmanager:postgresql://... with its driver-class-name; username is the secret’s nameconfiguration accepted; not run against AWS
Azure managed identityAzure’s login plugin class in the URL; no passwordconfiguration accepted; not run against Azure
GCP Cloud SQL IAMCloud SQL socket factory in the URL; no passwordconfiguration accepted; not run against GCP
Kerberosgsslib=gssapi in the URL, plus the JVM’s JAAS configuration; no passwordconfiguration accepted
Vault dynamic credentials (a new user every hour)-not supported yet on PostgreSQL and MySQL; on MariaDB, through a credentialType plugin

“Tested end to end” means one of Ekbatan’s integration tests logs in that way against a real database and checks both a migration’s connection and the application pool’s.

PostgreSQL’s driver, from 42.7.11, refuses a client key file that anyone but its owner can read, as psql does: give the key file chmod 600.

How migrations connect

FlywayMigrator hands Flyway connections made from the same DataSourceConfig as the application’s pools - same driver, driver-class-name and data-source-properties included - one new connection each time Flyway asks, closed when Flyway is done. So a migration logs in the way the application does, through a wrapper driver too, and nothing a migration sets on a connection can reach the application’s pool. A refused login fails at the first attempt, and Flyway’s own connectRetries still applies.