diff --git a/.github/workflows/docs_sync.yml b/.github/workflows/docs_sync.yml new file mode 100644 index 000000000..e1f73e2fe --- /dev/null +++ b/.github/workflows/docs_sync.yml @@ -0,0 +1,212 @@ +name: 'Sync docs to ClickHouse/ClickHouse' + +# Opens (or refreshes) a pull request on the aggregator docs repo +# +# It fires on three events: +# * a merged PR carrying the `sync-docs` label -> ship docs immediately, +# without waiting for a release +# * a published release -> ship docs on release, gated +# by RELEASE_SCOPE below (major-only by default); +# * manual dispatch -> force a sync. +# +# A single stable branch is force-pushed each run, so the bot keeps exactly one +# open PR that is always one commit off the target branch. The PR is labelled +# `pr-autogenerated-docs`, which the aggregator's docs check recognizes for the +# autogenerated-region edit guard. The generated body includes the changelog +# metadata required for the target repository's CI to initialize. + +on: + pull_request_target: + types: [closed] + release: + types: [published] + workflow_dispatch: + +# --------------------------------------------------------------------------- +# Configuration -- edit these to reuse this workflow in another client repo. +# --------------------------------------------------------------------------- +env: + # Repo that receives the docs PR, and the branch that PR targets. + TARGET_REPO: ClickHouse/ClickHouse + TARGET_BRANCH: master + # Path inside TARGET_REPO that mirrors this repo's docs. Wiped and replaced on + # every sync so deletions and renames propagate. + TARGET_DOCS_PATH: docs/integrations/language-clients/java + # This repo's docs file or folder (the source of truth). + SOURCE_DOCS_PATH: docs/clickhouse-docs + # Stable branch on TARGET_REPO, force-pushed each run. + SYNC_BRANCH: robot/docs-sync-clickhouse-java + # Label the sync PR gets on TARGET_REPO. + PR_LABEL: pr-autogenerated-docs + # Label on a merged PR in THIS repo that triggers the expedited path. + SYNC_LABEL: sync-docs + # Which releases trigger a sync: major (X.0.0), minor (X.Y.0), or all. + RELEASE_SCOPE: all + +# The cross-repo work is done with a GitHub App token (see the sync job); this +# workflow only needs to read its own repository. +permissions: + contents: read + +jobs: + # Gate: decide whether this event should produce a sync, and why. Kept in its + # own job so the (large) checkout/clone in `sync` only runs when needed. + decide: + runs-on: ubuntu-latest + outputs: + run: ${{ steps.gate.outputs.run }} + reason: ${{ steps.gate.outputs.reason }} + steps: + - name: Decide whether to sync + id: gate + env: + EVENT_NAME: ${{ github.event_name }} + PR_MERGED: ${{ github.event.pull_request.merged }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + RELEASE_PRERELEASE: ${{ github.event.release.prerelease }} + run: | + set -euo pipefail + run=false + reason="" + case "$EVENT_NAME" in + workflow_dispatch) + # Manual runs always sync -- the operator explicitly asked for it. + run=true + reason="manual dispatch" + ;; + pull_request_target) + # Expedited path: a merged PR that carries the sync label. + if [[ "$PR_MERGED" == "true" ]] \ + && printf '%s' "$PR_LABELS" | jq -e --arg l "$SYNC_LABEL" 'index($l) != null' >/dev/null; then + run=true + reason="merged PR #${PR_NUMBER} labeled '${SYNC_LABEL}'" + fi + ;; + release) + if [[ "$RELEASE_PRERELEASE" == "true" ]]; then + echo "Release ${RELEASE_TAG} is a prerelease; skipping." + elif [[ "${RELEASE_TAG#v}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + minor="${BASH_REMATCH[2]}" + patch="${BASH_REMATCH[3]}" + case "$RELEASE_SCOPE" in + all) run=true ;; + minor) [[ "$patch" == "0" ]] && run=true ;; + major|*) [[ "$minor" == "0" && "$patch" == "0" ]] && run=true ;; + esac + [[ "$run" == "true" ]] && reason="release ${RELEASE_TAG} (scope=${RELEASE_SCOPE})" + else + echo "Could not parse release tag '${RELEASE_TAG}'; skipping." + fi + ;; + esac + echo "Decision: run=${run} reason='${reason}'" + { + echo "run=${run}" + echo "reason=${reason}" + } >> "$GITHUB_OUTPUT" + + sync: + needs: decide + if: needs.decide.outputs.run == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout docs source + uses: actions/checkout@v5 + with: + # On a release, take the docs as of the released tag; otherwise take + # the default checkout (post-merge default branch / dispatched ref). + ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }} + + - name: Parse target repo + id: parse + run: | + set -euo pipefail + echo "owner=${TARGET_REPO%%/*}" >> "$GITHUB_OUTPUT" + echo "name=${TARGET_REPO##*/}" >> "$GITHUB_OUTPUT" + + - name: Generate token for target repo + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.WORKFLOW_AUTH_PUBLIC_APP_ID }} + private-key: ${{ secrets.WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY }} + owner: ${{ steps.parse.outputs.owner }} + repositories: ${{ steps.parse.outputs.name }} + + - name: Sync docs and open/refresh PR + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + REASON: ${{ needs.decide.outputs.reason }} + SOURCE_REPO: ${{ github.repository }} + SOURCE_REF: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref_name }} + SOURCE_SHA: ${{ github.sha }} + run: | + set -euo pipefail + + # Resolve the absolute source path before we cd elsewhere. + src="$(realpath "$SOURCE_DOCS_PATH")" + + workdir="$(mktemp -d)" + git clone --depth 1 --branch "$TARGET_BRANCH" \ + "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPO}.git" "$workdir" + cd "$workdir" + + # Replace the target file or folder with this repo's docs. Wipe first + # so deletions and renames on our side propagate. + target="${workdir:?}/${TARGET_DOCS_PATH}" + rm -rf "$target" + mkdir -p "$(dirname "$target")" + if [[ -d "$src" ]]; then + mkdir -p "$target" + cp -R "${src}/." "${target}/" + else + cp "$src" "$target" + fi + + git config user.name "clickhouse-docs-bot" + git config user.email "clickhouse-docs-bot@users.noreply.github.com" + + # Build the single-commit-off-target branch fresh from the target + # branch so the PR is always exactly one commit ahead. + git checkout -B "$SYNC_BRANCH" + git add -A -- "$TARGET_DOCS_PATH" + if git diff --cached --quiet; then + echo "No docs changes to sync; nothing to do." + exit 0 + fi + + git commit -m "Sync ${SOURCE_REPO} docs (${REASON})" + git push --force origin "$SYNC_BRANCH" + + title="Docs: Sync ${SOURCE_REPO} docs" + body=$(cat < + + + + + +Java client library to communicate with a DB server through its protocols. The current implementation only supports the [HTTP interface](/concepts/features/interfaces/http). +The library provides its own API to send requests to a server. The library also provides tools to work with different binary data formats (RowBinary* & Native*). + +## Setup {#setup} + +- Maven Central (project web page): https://mvnrepository.com/artifact/com.clickhouse/client-v2 +- Nightly builds (repository link): https://central.sonatype.com/repository/maven-snapshots/ +
+ + + +```xml + + com.clickhouse + client-v2 + 0.9.8 + +``` + + + + +```kotlin +// https://mvnrepository.com/artifact/com.clickhouse/client-v2 +implementation("com.clickhouse:client-v2:0.9.8") +``` + + + +```groovy +// https://mvnrepository.com/artifact/com.clickhouse/client-v2 +implementation 'com.clickhouse:client-v2:0.9.8' +``` + + + +
+ +## Initialization {#initialization} + +The Client object is initialized by `com.clickhouse.client.api.Client.Builder#build()`. Each client has its own context and no objects are shared between them. +The Builder has configuration methods for convenient setup. + +Example: +```java showLineNumbers + Client client = new Client.Builder() + .addEndpoint("https://clickhouse-cloud-instance:8443/") + .setUsername(user) + .setPassword(password) + .build(); +``` + +`Client` is `AutoCloseable` and should be closed when not needed anymore. + +### Authentication {#authentication} + +Authentication is configured per client at the initialization phase. There are three authentication methods supported: by password, by access token, by SSL Client Certificate. + +Authentication by a password requires setting user name password by calling `setUsername(String)` and `setPassword(String)`: +```java showLineNumbers + Client client = new Client.Builder() + .addEndpoint("https://clickhouse-cloud-instance:8443/") + .setUsername(user) + .setPassword(password) + .build(); +``` + +Authentication by an access token requires setting access token by calling `setAccessToken(String)`: +```java showLineNumbers + Client client = new Client.Builder() + .addEndpoint("https://clickhouse-cloud-instance:8443/") + .setAccessToken(userAccessToken) + .build(); +``` + +Authentication by a SSL Client Certificate require setting username, enabling SSL Authentication, setting a client certificate and a client key by calling `setUsername(String)`, `useSSLAuthentication(boolean)`, `setClientCertificate(String)` and `setClientKey(String)` accordingly: +```java showLineNumbers +Client client = new Client.Builder() + .useSSLAuthentication(true) + .setUsername("some_user") + .setClientCertificate("some_user.crt") + .setClientKey("some_user.key") +``` + + +SSL Authentication may be hard to troubleshoot on production because many errors from SSL libraries provide not enough information. For example, if client certificate and key don't match then server will terminate connection immediately (in case of HTTP it will be connection initiation stage where no HTTP requests are send so no response is sent). + +Please use tools like [openssl](https://docs.openssl.org/master/man1/openssl/) to verify certificates and keys: +- check key integrity: `openssl rsa -in [key-file.key] -check -noout` +- check client certificate has matching CN for a user: + - get CN from an user certificate - `openssl x509 -noout -subject -in [user.cert]` + - verify same value is set in database `select name, auth_type, auth_params from system.users where auth_type = 'ssl_certificate'` (query will output `auth_params` with something like ` {"common_names":["some_user"]}`) + + +## Configuration {#configuration} + +All settings are defined by instance methods (a.k.a configuration methods) that make the scope and context of each value clear. +Major configuration parameters are defined in one scope (client or operation) and don't override each other. + +Configuration is defined during client creation. See `com.clickhouse.client.api.Client.Builder`. + +## Client Configuration {#client-configuration} + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `addEndpoint(String endpoint)` | `endpoint` - URL formatted server address | Adds a server endpoint to list of available servers. Currently only one endpoint is supported. | `none` | `none` | +| `addEndpoint(Protocol protocol, String host, int port, boolean secure)` | `protocol` - connection protocol
`host` - IP or hostname
`secure` - use HTTPS | Adds a server endpoint to list of available servers. Currently only one endpoint is supported. | `none` | `none` | +| `enableConnectionPool(boolean enable)` | `enable` - flag to enable/disable | Sets if a connection pool is enabled | `true` | `connection_pool_enabled` | +| `setMaxConnections(int maxConnections)` | `maxConnections` - number of connections | Sets how many connections can a client open to each server endpoint. | `10` | `max_open_connections` | +| `setConnectionTTL(long timeout, ChronoUnit unit)` | `timeout` - timeout value
`unit` - time unit | Sets connection TTL after which connection will be considered as not active | `-1` | `connection_ttl` | +| `setKeepAliveTimeout(long timeout, ChronoUnit unit)` | `timeout` - timeout value
`unit` - time unit | Sets HTTP connection keep-alive timeout. Set to `0` to disable Keep-Alive. | - | `http_keep_alive_timeout` | +| `setConnectionReuseStrategy(ConnectionReuseStrategy strategy)` | `strategy` - `LIFO` or `FIFO` | Selects which strategy connection pool should use | `FIFO` | `connection_reuse_strategy` | +| `setDefaultDatabase(String database)` | `database` - name of a database | Sets default database. | `default` | `database` | + +
+ + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setUsername(String username)` | `username` - username for authentication | Sets username for an authentication method that is selected by further configuration | `default` | `user` | +| `setPassword(String password)` | `password` - secret value | Sets a secret for password authentication and effectively selects as authentication method | - | `password` | +| `setAccessToken(String accessToken)` | `accessToken` - access token string | Sets an access token to authenticate with a sets corresponding authentication method | - | `access_token` | +| `useSSLAuthentication(boolean useSSLAuthentication)` | `useSSLAuthentication` - flag to enable SSL auth | Sets SSL Client Certificate as an authentication method. | - | `ssl_authentication` | +| `useHTTPBasicAuth(boolean useBasicAuth)` | `useBasicAuth` - flag to enable/disable | Sets if basic HTTP authentication should be used for user-password authentication. Resolves issues with passwords containing special characters. | `true` | `http_use_basic_auth` | +| `useBearerTokenAuth(String bearerToken)` | `bearerToken` - an encoded bearer token | Specifies whether to use Bearer Authentication and what token to use. The token will be sent as is. | - | `bearer_token` | + + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setConnectTimeout(long timeout, ChronoUnit unit)` | `timeout` - timeout value
`unit` - time unit | Sets connection initiation timeout for any outgoing connection. | - | `connection_timeout` | +| `setConnectionRequestTimeout(long timeout, ChronoUnit unit)` | `timeout` - timeout value
`unit` - time unit | Sets connection request timeout. This take effect only for getting connection from a pool. | `10000` | `connection_request_timeout` | +| `setSocketTimeout(long timeout, ChronoUnit unit)` | `timeout` - timeout value
`unit` - time unit | Sets socket timeout that affects read and write operations | `0` | `socket_timeout` | +| `setExecutionTimeout(long timeout, ChronoUnit timeUnit)` | `timeout` - timeout value
`timeUnit` - time unit | Sets maximum execution timeout for queries | `0` | `max_execution_time` | +| `retryOnFailures(ClientFaultCause ...causes)` | `causes` - enum constant of `ClientFaultCause` | Sets recoverable/retriable fault types. | `NoHttpResponse` `ConnectTimeout` `ConnectionRequestTimeout` | `client_retry_on_failures` | +| `setMaxRetries(int maxRetries)` | `maxRetries` - number of retries | Sets maximum number of retries for failures defined by `retryOnFailures` | `3` | `retry` | + +
+ + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setSocketRcvbuf(long size)` | `size` - size in bytes | Sets TCP socket receive buffer. This buffer out of the JVM memory. | `8196` | `socket_rcvbuf` | +| `setSocketSndbuf(long size)` | `size` - size in bytes | Sets TCP socket send buffer. This buffer out of the JVM memory. | `8196` | `socket_sndbuf` | +| `setSocketKeepAlive(boolean value)` | `value` - flag to enable/disable | Sets option `SO_KEEPALIVE` for every TCP socket. TCP Keep Alive enables mechanism that will check liveness of the connection. | - | `socket_keepalive` | +| `setSocketTcpNodelay(boolean value)` | `value` - flag to enable/disable | Sets option `SO_NODELAY` for every TCP socket. This TCP option will make socket to push data as soon as possible. | - | `socket_tcp_nodelay` | +| `setSocketLinger(int secondsToWait)` | `secondsToWait` - number of seconds | Set linger time for every TCP socket created by the client. | - | `socket_linger` | + + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `compressServerResponse(boolean enabled)` | `enabled` - flag to enable/disable | Sets if server should compress its responses. | `true` | `compress` | +| `compressClientRequest(boolean enabled)` | `enabled` - flag to enable/disable | Sets if client should compress its requests. | `false` | `decompress` | +| `useHttpCompression(boolean enabled)` | `enabled` - flag to enable/disable | Sets if HTTP compression should be used for client/server communications if corresponding options are enabled | - | - | +| `appCompressedData(boolean enabled)` | `enabled` - flag to enable/disable | Tell client that compression will be handled by application. | `false` | `app_compressed_data` | +| `setLZ4UncompressedBufferSize(int size)` | `size` - size in bytes | Sets size of a buffer that will receive uncompressed portion of a data stream. | `65536` | `compression.lz4.uncompressed_buffer_size` | +| `disableNativeCompression` | `disable` - flag to disable | Disable native compression. If set to true then native compression will be disabled. | `false` | `disable_native_compression` | + + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setSSLTrustStore(String path)` | `path` - file path on local system | Sets if client should use SSL truststore for server host validation. | - | `trust_store` | +| `setSSLTrustStorePassword(String password)` | `password` - secret value | Sets password to be used to unlock SSL truststore specified by `setSSLTrustStore` | - | `key_store_password` | +| `setSSLTrustStoreType(String type)` | `type` - truststore type name | Sets type of the truststore specified by `setSSLTrustStore`. | - | `key_store_type` | +| `setRootCertificate(String path)` | `path` - file path on local system | Sets if client should use specified root (CA) certificate for server host to validation. | - | `sslrootcert` | +| `setClientCertificate(String path)` | `path` - file path on local system | Sets client certificate path to be used while initiating SSL connection and to be used by SSL authentication. | - | `sslcert` | +| `setClientKey(String path)` | `path` - file path on local system | Sets client private key to be used for encrypting SSL communication with a server. | - | `ssl_key` | +| `sslSocketSNI(String sni)` | `sni` - server name string | Sets server name to be used for SNI (Server Name Indication) in SSL/TLS connection. | - | `ssl_socket_sni` | + + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `addProxy(ProxyType type, String host, int port)` | `type` - proxy type
`host` - proxy hostname or IP
`port` - proxy port | Sets proxy to be used for communication with a server. | - | `proxy_type`, `proxy_host`, `proxy_port` | +| `setProxyCredentials(String user, String pass)` | `user` - proxy username
`pass` - password | Sets user credentials to authenticate with a proxy. | - | `proxy_user`, `proxy_password` | + +
+ + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setHttpCookiesEnabled(boolean enabled)` | `enabled` - flag to enable/disable | Set if HTTP cookies should be remembered and sent to server back. | - | - | +| `httpHeader(String key, String value)` | `key` - HTTP header key
`value` - string value | Sets value for a single HTTP header. Previous value is overridden. | `none` | `none` | +| `httpHeader(String key, Collection values)` | `key` - HTTP header key
`values` - list of string values | Sets values for a single HTTP header. Previous value is overridden. | `none` | `none` | +| `httpHeaders(Map headers)` | `headers` - map with HTTP headers | Sets multiple HTTP header values at a time. | `none` | `none` | +| `useHttpFormDataForQuery(boolean enable)` | `enable` - flag to enable/disable | Sets whether query parameters should be sent as HTTP form data in the request body instead of the URL. Works only with server-side compression. If client-level compression is enabled, it will be disabled for query requests with parameters, as each parameter is sent as multipart content. | `false` | `client.http.use_form_request_for_query` | + +
+ + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `serverSetting(String name, String value)` | `name` - setting name
`value` - setting value | Sets what settings to pass to server along with each query. Individual operation settings may override it. [List of settings](/concepts/features/configuration/settings/settings-query-level) | `none` | `none` | +| `serverSetting(String name, Collection values)` | `name` - setting name
`values` - setting values | Sets what settings to pass to server with multiple values, for example [roles](/concepts/features/interfaces/http#setting-role-with-query-parameters) | `none` | `none` | +| `setOption("custom_settings_prefix", value)` | `value` - prefix string | Sets the prefix for custom settings passed to the server. Should be aligned with the server configuration. See [ClickHouse Docs](/concepts/features/configuration/settings/settings-query-level#custom_settings). | `custom_` | `custom_settings_prefix` | + +
+ + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `useServerTimeZone(boolean useServerTimeZone)` | `useServerTimeZone` - flag to enable/disable | Sets if client should use server timezone when decoding DateTime and Date column values. | `true` | `use_server_time_zone` | +| `useTimeZone(String timeZone)` | `timeZone` - java valid timezone ID | Sets if specified timezone should be used when decoding DateTime and Date column values. Will override server timezone. | - | `use_time_zone` | +| `setServerTimeZone(String timeZone)` | `timeZone` - java valid timezone ID | Sets server side timezone. UTC timezone will be used by default. | `UTC` | `server_time_zone` | + + + + + +| Method | Arguments | Description | Default | Key | +|--------|-----------|-------------|---------|-----| +| `setOption(String key, String value)` | `key` - configuration option key
`value` - option value | Sets raw value of client options. Useful when reading configuration from properties files. | - | - | +| `useAsyncRequests(boolean async)` | `async` - flag to enable/disable | Sets if client should execute request in a separate thread. Disabled by default because application knows better how to organize multi-threaded tasks. | `false` | `async` | +| `setSharedOperationExecutor(ExecutorService executorService)` | `executorService` - executor service instance | Sets executor service for operation tasks. | `none` | `none` | +| `setQueryIdGenerator(Supplier supplier)` | `supplier` - a `Supplier` that generates query IDs | Sets a custom query ID generator used when no query ID is specified in operation settings (`InsertSettings`, `QuerySettings`). | - | - | +| `setClientNetworkBufferSize(int size)` | `size` - size in bytes | Sets size of a buffer in application memory space that is used to copy data between socket and application. | `300000` | `client_network_buffer_size` | +| `allowBinaryReaderToReuseBuffers(boolean reuse)` | `reuse` - flag to enable/disable | If enabled, reader will use preallocated buffers to do numbers transcoding. Reduces GC pressure for numeric data. | - | - | +| `columnToMethodMatchingStrategy(ColumnToMethodMatchingStrategy strategy)` | `strategy` - matching strategy implementation | Sets custom strategy to be used for matching DTO class fields and DB columns when registering DTO. | `none` | `none` | +| `setClientName(String clientName)` | `clientName` - application name string | Sets additional information about calling application. Will be passed as `User-Agent` header. | - | `client_name` | +| `registerClientMetrics(Object registry, String name)` | `registry` - Micrometer registry instance
`name` - metrics group name | Registers sensors with Micrometer (https://micrometer.io/) registry instance. | - | - | +| `setServerVersion(String version)` | `version` - server version string | Sets server version to avoid version detection. | - | `server_version` | +| `typeHintMapping(Map typeHintMapping)` | `typeHintMapping` - map of type hints | Sets type hint mapping for ClickHouse types. For example, to make multidimensional arrays be present as Java containers. | - | `type_hint_mapping` | + +
+
+
+ +### Client Identification {#client-identification} + +There are two fields in a query log that identify application originated a request: `client_name` and `http_user_agent`. Native TCP protocol uses +`client_name` to identify application. HTTP protocol uses `http_user_agent` to identify application. Client builder has method `setClientName` correct values +for both protocols. +The field `http_user_agent` is set according to `User-Agent` header common format: `application-name[/version] [(operating-system; architecture; ...)]`. +This set of values is repeated for each layer: application, client library, http client library. What is set by `setClientName` method comes first in the list. + +For example: +```java showLineNumbers +client.setClientName("my-app-01/1.0"); +``` +will result in the following `http_user_agent` value: +``` +my-app-01/1.0 clickhouse-java-v2/0.9.6-SNAPSHOT (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4 +``` + +Application can set own http header `User-Agent` to identify itself. But part `clickhouse-java-v2/0.9.6-SNAPSHOT` will be appended to the end of the header. + +### Operation Identification {#operation-identification} + +Query log has another two fields `query_id` and `log_comment` that can be used to identify an operation and add additional information to the query log. + +`query_id` is a unique identifier of an operation. It can be set by application by calling `setQueryId` method of the `QuerySettings` class. +```java showLineNumbers +QuerySettings querySettings = new QuerySettings(); +querySettings.setQueryId("some-query-id"); +``` + +`log_comment` is a comment that can be added to the query log. It can be set by application by calling `logComment` method of the `QuerySettings` class. +```java showLineNumbers +QuerySettings querySettings = new QuerySettings(); +querySettings.logComment("some-comment"); +``` + +### Server Settings {#server-settings} + +Server side settings can be set on the client level once while creation (see `serverSetting` method of the `Builder`) and on operation level (see `serverSetting` for operation settings class). + +```java showLineNumbers + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost", mockServer.port(), false) + .setUsername("default") + .setPassword(ClickHouseServerForTest.getPassword()) + .compressClientRequest(true) + + // Client level + .serverSetting("max_threads", "10") + .serverSetting("async_insert", "1") + .serverSetting("roles", Arrays.asList("role1", "role2")) + + .build()) { + + // Operation level + QuerySettings querySettings = new QuerySettings(); + querySettings.serverSetting("session_timezone", "Europe/Zurich"); + + ... +} +``` +⚠️ When options are set via `setOption` method (either the `Client.Builder` or operation settings class) then server settings name should be prefixed with `clickhouse_setting_`. The `com.clickhouse.client.api.ClientConfigProperties#serverSetting()` may be handy in this case. + +### Custom HTTP Header + +Custom HTTP headers can be set for all operations (client level) or a single one (operation level). +```java showLineNumbers + +QuerySettings settings = new QuerySettings() + .httpHeader(HttpHeaders.REFERER, clientReferer) + .setQueryId(qId); + +``` + +When options are set via `setOption` method (either the `Client.Builder` or operation settings class) then custom header name should be prefixed with `http_header_`. Method `com.clickhouse.client.api.ClientConfigProperties#httpHeader()` may be handy in this case. + +## Common Definitions {#common-definitions} + +### ClickHouseFormat {#clickhouseformat} + +Enum of [supported formats](/reference/formats/index). It includes all formats that ClickHouse supports. + +* `raw` - user should transcode raw data +* `full` - the client can transcode data by itself and accepts a raw data stream +* `-` - operation not supported by ClickHouse for this format + +This client version supports: + +| Format | Input | Output | +|-------------------------------------------------------------------------------------------------------------------------------|:------:|:-------:| +| [TabSeparated](/reference/formats/TabSeparated/TabSeparated) | raw | raw | +| [TabSeparatedRaw](/reference/formats/TabSeparated/TabSeparatedRaw) | raw | raw | +| [TabSeparatedWithNames](/reference/formats/TabSeparated/TabSeparatedWithNames) | raw | raw | +| [TabSeparatedWithNamesAndTypes](/reference/formats/TabSeparated/TabSeparatedWithNamesAndTypes) | raw | raw | +| [TabSeparatedRawWithNames](/reference/formats/TabSeparated/TabSeparatedRawWithNames) | raw | raw | +| [TabSeparatedRawWithNamesAndTypes](/reference/formats/TabSeparated/TabSeparatedRawWithNamesAndTypes) | raw | raw | +| [Template](/reference/formats/Template/Template) | raw | raw | +| [TemplateIgnoreSpaces](/reference/formats/Template/TemplateIgnoreSpaces) | raw | - | +| [CSV](/reference/formats/CSV/CSV) | raw | raw | +| [CSVWithNames](/reference/formats/CSV/CSVWithNames) | raw | raw | +| [CSVWithNamesAndTypes](/reference/formats/CSV/CSVWithNamesAndTypes) | raw | raw | +| [CustomSeparated](/reference/formats/CustomSeparated/CustomSeparated) | raw | raw | +| [CustomSeparatedWithNames](/reference/formats/CustomSeparated/CustomSeparatedWithNames) | raw | raw | +| [CustomSeparatedWithNamesAndTypes](/reference/formats/CustomSeparated/CustomSeparatedWithNamesAndTypes) | raw | raw | +| [SQLInsert](/reference/formats/SQLInsert) | - | raw | +| [Values](/reference/formats/Values) | raw | raw | +| [Vertical](/reference/formats/Vertical) | - | raw | +| [JSON](/reference/formats/JSON/JSON) | raw | raw | +| [JSONAsString](/reference/formats/JSON/JSONAsString) | raw | - | +| [JSONAsObject](/reference/formats/JSON/JSONAsObject) | raw | - | +| [JSONStrings](/reference/formats/JSON/JSONStrings) | raw | raw | +| [JSONColumns](/reference/formats/JSON/JSONColumns) | raw | raw | +| [JSONColumnsWithMetadata](/reference/formats/JSON/JSONColumnsWithMetadata) | raw | raw | +| [JSONCompact](/reference/formats/JSON/JSONCompact) | raw | raw | +| [JSONCompactStrings](/reference/formats/JSON/JSONCompactStrings) | - | raw | +| [JSONCompactColumns](/reference/formats/JSON/JSONCompactColumns) | raw | raw | +| [JSONEachRow](/reference/formats/JSON/JSONEachRow) | raw | raw | +| [PrettyJSONEachRow](/reference/formats/JSON/PrettyJSONEachRow) | - | raw | +| [JSONEachRowWithProgress](/reference/formats/JSON/JSONEachRowWithProgress) | - | raw | +| [JSONStringsEachRow](/reference/formats/JSON/JSONStringsEachRow) | raw | raw | +| [JSONStringsEachRowWithProgress](/reference/formats/JSON/JSONStringsEachRowWithProgress) | - | raw | +| [JSONCompactEachRow](/reference/formats/JSON/JSONCompactEachRow) | raw | raw | +| [JSONCompactEachRowWithNames](/reference/formats/JSON/JSONCompactEachRowWithNames) | raw | raw | +| [JSONCompactEachRowWithNamesAndTypes](/reference/formats/JSON/JSONCompactEachRowWithNamesAndTypes) | raw | raw | +| [JSONCompactStringsEachRow](/reference/formats/JSON/JSONCompactStringsEachRow) | raw | raw | +| [JSONCompactStringsEachRowWithNames](/reference/formats/JSON/JSONCompactStringsEachRowWithNames) | raw | raw | +| [JSONCompactStringsEachRowWithNamesAndTypes](/reference/formats/JSON/JSONCompactStringsEachRowWithNamesAndTypes) | raw | raw | +| [JSONObjectEachRow](/reference/formats/JSON/JSONObjectEachRow) | raw | raw | +| [BSONEachRow](/reference/formats/BSONEachRow) | raw | raw | +| [TSKV](/reference/formats/TabSeparated/TSKV) | raw | raw | +| [Pretty](/reference/formats/Pretty/Pretty) | - | raw | +| [PrettyNoEscapes](/reference/formats/Pretty/PrettyNoEscapes) | - | raw | +| [PrettyMonoBlock](/reference/formats/Pretty/PrettyMonoBlock) | - | raw | +| [PrettyNoEscapesMonoBlock](/reference/formats/Pretty/PrettyNoEscapesMonoBlock) | - | raw | +| [PrettyCompact](/reference/formats/Pretty/PrettyCompact) | - | raw | +| [PrettyCompactNoEscapes](/reference/formats/Pretty/PrettyCompactNoEscapes) | - | raw | +| [PrettyCompactMonoBlock](/reference/formats/Pretty/PrettyCompactMonoBlock) | - | raw | +| [PrettyCompactNoEscapesMonoBlock](/reference/formats/Pretty/PrettyCompactNoEscapesMonoBlock) | - | raw | +| [PrettySpace](/reference/formats/Pretty/PrettySpace) | - | raw | +| [PrettySpaceNoEscapes](/reference/formats/Pretty/PrettySpaceNoEscapes) | - | raw | +| [PrettySpaceMonoBlock](/reference/formats/Pretty/PrettySpaceMonoBlock) | - | raw | +| [PrettySpaceNoEscapesMonoBlock](/reference/formats/Pretty/PrettySpaceNoEscapesMonoBlock) | - | raw | +| [Prometheus](/reference/formats/Prometheus) | - | raw | +| [Protobuf](/reference/formats/Protobuf/Protobuf) | raw | raw | +| [ProtobufSingle](/reference/formats/Protobuf/ProtobufSingle) | raw | raw | +| [ProtobufList](/reference/formats/Protobuf/ProtobufList) | raw | raw | +| [Avro](/reference/formats/Avro/Avro) | raw | raw | +| [AvroConfluent](/reference/formats/Avro/AvroConfluent) | raw | - | +| [Parquet](/reference/formats/Parquet/Parquet) | raw | raw | +| [ParquetMetadata](/reference/formats/Parquet/ParquetMetadata) | raw | - | +| [Arrow](/reference/formats/Arrow/Arrow) | raw | raw | +| [ArrowStream](/reference/formats/Arrow/ArrowStream) | raw | raw | +| [ORC](/reference/formats/ORC) | raw | raw | +| [One](/reference/formats/One) | raw | - | +| [Npy](/reference/formats/Npy) | raw | raw | +| [RowBinary](/reference/formats/RowBinary/RowBinary) | full | full | +| [RowBinaryWithNames](/reference/formats/RowBinary/RowBinaryWithNamesAndTypes) | full | full | +| [RowBinaryWithNamesAndTypes](/reference/formats/RowBinary/RowBinaryWithNamesAndTypes) | full | full | +| [RowBinaryWithDefaults](/reference/formats/RowBinary/RowBinaryWithDefaults) | full | - | +| [Native](/reference/formats/Native) | full | raw | +| [Null](/reference/formats/Null) | - | raw | +| [XML](/reference/formats/XML) | - | raw | +| [CapnProto](/reference/formats/CapnProto) | raw | raw | +| [LineAsString](/reference/formats/LineAsString/LineAsString) | raw | raw | +| [Regexp](/reference/formats/Regexp) | raw | - | +| [RawBLOB](/reference/formats/RawBLOB) | raw | raw | +| [MsgPack](/reference/formats/MsgPack) | raw | raw | +| [MySQLDump](/reference/formats/MySQLDump) | raw | - | +| [DWARF](/reference/formats/DWARF) | raw | - | +| [Markdown](/reference/formats/Markdown) | - | raw | +| [Form](/reference/formats/Form) | raw | - | + +## Insert API {#insert-api} + +### insert(String tableName, InputStream data, ClickHouseFormat format) {#insertstring-tablename-inputstream-data-clickhouseformat-format} + +Accepts data as an `InputStream` of bytes in the specified format. It is expected that `data` is encoded in the `format`. + +**Signatures** + +```java +CompletableFuture insert(String tableName, InputStream data, ClickHouseFormat format, InsertSettings settings) +CompletableFuture insert(String tableName, InputStream data, ClickHouseFormat format) +``` + +**Parameters** + +`tableName` - a target table name. + +`data` - an input stream of an encoded data. + +`format` - a format in which the data is encoded. + +`settings` - request settings. + +**Return value** + +Future of `InsertResponse` type - result of the operation and additional information like server side metrics. + +**Examples** + +```java showLineNumbers +try (InputStream dataStream = getDataStream()) { + try (InsertResponse response = client.insert(TABLE_NAME, dataStream, ClickHouseFormat.JSONEachRow, + insertSettings).get(3, TimeUnit.SECONDS)) { + + log.info("Insert finished: {} rows written", response.getMetrics().getMetric(ServerMetrics.NUM_ROWS_WRITTEN).getLong()); + } catch (Exception e) { + log.error("Failed to write JSONEachRow data", e); + throw new RuntimeException(e); + } +} + +``` + +### insert(String tableName, List<?> data, InsertSettings settings) {#insertstring-tablename-listlt-data-insertsettings-settings} + +Sends a write request to database. The list of objects is converted into an efficient format and then is sent to a server. The class of the list items should be registered up-front using `register(Class, TableSchema)` method. + +**Signatures** +```java +client.insert(String tableName, List data, InsertSettings settings) +client.insert(String tableName, List data) +``` + +**Parameters** + +`tableName` - name of the target table. + +`data` - collection DTO (Data Transfer Object) objects. + +`settings` - request settings. + +**Return value** + +Future of `InsertResponse` type - the result of the operation and additional information like server side metrics. + +**Examples** + +```java showLineNumbers +// Important step (done once) - register class to pre-compile object serializer according to the table schema. +client.register(ArticleViewEvent.class, client.getTableSchema(TABLE_NAME)); + +List events = loadBatch(); + +try (InsertResponse response = client.insert(TABLE_NAME, events).get()) { + // handle response, then it will be closed and connection that served request will be released. +} +``` + +### InsertSettings {#insertsettings} + +Configuration options for insert operations. + +**Configuration methods** + +| Method | Description | +|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| `setQueryId(String queryId)` | Sets query ID that will be assigned to the operation. Default: `null`. | +| `setDeduplicationToken(String token)` | Sets the deduplication token. This token will be sent to the server and can be used to identify the query. Default: `null`. | +| `setInputStreamCopyBufferSize(int size)` | Copy buffer size. The buffer is used during write operations to copy data from user-provided input stream to an output stream. Default: `8196`. | +| `serverSetting(String name, String value)` | Sets individual server settings for an operation. | +| `serverSetting(String name, Collection values)` | Sets individual server settings with multiple values for an operation. Items of the collection should be `String` values. | +| `setDBRoles(Collection dbRoles)` | Sets DB roles to be set before executing an operation. Items of the collection should be `String` values. | +| `setOption(String option, Object value)` | Sets a configuration option in raw format. This isn't a server setting. | + +### InsertResponse {#insertresponse} + +Response object that holds result of insert operation. It is only available if the client got response from a server. + + +This object should be closed as soon as possible to release a connection because the connection can't be re-used until all data of previous response is fully read. + + +| Method | Description | +|-----------------------------|------------------------------------------------------------------------------------------------------| +| `OperationMetrics getMetrics()` | Returns object with operation metrics. | +| `String getQueryId()` | Returns query ID assigned for the operation by the application (through operation settings or by server). | + +## Query API {#query-api} + +### query(String sqlQuery) {#querystring-sqlquery} + +Sends `sqlQuery` as is. Response format is set by query settings. `QueryResponse` will hold a reference to the response stream that should be consumed by a reader for the supportig format. + +**Signatures** + +```java +CompletableFuture query(String sqlQuery, QuerySettings settings) +CompletableFuture query(String sqlQuery) +``` + +**Parameters** + +`sqlQuery` - a single SQL statement. The Query is sent as is to a server. + +`settings` - request settings. + +**Return value** + +Future of `QueryResponse` type - a result dataset and additional information like server side metrics. The Response object should be closed after consuming the dataset. + +**Examples** + +```java +final String sql = "select * from " + TABLE_NAME + " where title <> '' limit 10"; + +// Default format is RowBinaryWithNamesAndTypesFormatReader so reader have all information about columns +try (QueryResponse response = client.query(sql).get(3, TimeUnit.SECONDS);) { + + // Create a reader to access the data in a convenient way + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + while (reader.hasNext()) { + reader.next(); // Read the next record from stream and parse it + + // get values + double id = reader.getDouble("id"); + String title = reader.getString("title"); + String url = reader.getString("url"); + + // collecting data + } +} catch (Exception e) { + log.error("Failed to read data", e); +} + +// put business logic outside of the reading block to release http connection asap. +``` + +### query(String sqlQuery, Map<String, Object> queryParams, QuerySettings settings) {#querystring-sqlquery-mapltstring-object-queryparams-querysettings-settings} + +Sends `sqlQuery` as is. Additionally will send query parameters so the server can compile the SQL expression. + +**Signatures** +```java +CompletableFuture query(String sqlQuery, Map queryParams, QuerySettings settings) +``` + +**Parameters** + +`sqlQuery` - sql expression with placeholders `{}`. + +`queryParams` - map of variables to complete the sql expression on server. + +`settings` - request settings. + +**Return value** + +Future of `QueryResponse` type - a result dataset and additional information like server side metrics. The Response object should be closed after consuming the dataset. + +**Examples** + +```java showLineNumbers + +// define parameters. They will be sent to the server along with the request. +Map queryParams = new HashMap<>(); +queryParams.put("param1", 2); + +try (QueryResponse response = + client.query("SELECT * FROM " + table + " WHERE col1 >= {param1:UInt32}", queryParams, new QuerySettings()).get()) { + + // Create a reader to access the data in a convenient way + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + while (reader.hasNext()) { + reader.next(); // Read the next record from stream and parse it + + // reading data + } + +} catch (Exception e) { + log.error("Failed to read data", e); +} + +``` + +### queryAll(String sqlQuery) {#queryallstring-sqlquery} + +Queries a data in `RowBinaryWithNamesAndTypes` format. Returns the result as a collection. Read performance is the same as with the reader but more memory is required to hold the whole dataset. + +**Signatures** +```java +List queryAll(String sqlQuery) +``` + +**Parameters** + +`sqlQuery` - sql expression to query data from a server. + +**Return value** + +Complete dataset represented by a list of `GenericRecord` objects that provide access in row style for the result data. + +**Examples** + +```java showLineNumbers +try { + log.info("Reading whole table and process record by record"); + final String sql = "select * from " + TABLE_NAME + " where title <> ''"; + + // Read whole result set and process it record by record + client.queryAll(sql).forEach(row -> { + double id = row.getDouble("id"); + String title = row.getString("title"); + String url = row.getString("url"); + + log.info("id: {}, title: {}, url: {}", id, title, url); + }); +} catch (Exception e) { + log.error("Failed to read data", e); +} +``` + +### QuerySettings {#querysettings} + +Configuration options for query operations. + +**Configuration methods** + +| Method | Description | +|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| `setQueryId(String queryId)` | Sets query ID that will be assigned to the operation. | +| `setFormat(ClickHouseFormat format)` | Sets response format. See `RowBinaryWithNamesAndTypes` for the full list. | +| `setMaxExecutionTime(Integer maxExecutionTime)` | Sets operation execution time on server. Won't affect read timeout. | +| `waitEndOfQuery(Boolean waitEndOfQuery)` | Requests the server to wait for the end of the query before sending a response. | +| `setUseServerTimeZone(Boolean useServerTimeZone)` | Server timezone (see client config) will be used to parse date/time types in the result of an operation. Default `false`. | +| `setUseTimeZone(String timeZone)` | Requests server to use `timeZone` for time conversion. See [session_timezone](/reference/settings/session-settings#session_timezone). | +| `serverSetting(String name, String value)` | Sets individual server settings for an operation. | +| `serverSetting(String name, Collection values)` | Sets individual server settings with multiple values for an operation. Items of the collection should be `String` values. | +| `setDBRoles(Collection dbRoles)` | Sets DB roles to be set before executing an operation. Items of the collection should be `String` values. | +| `setOption(String option, Object value)` | Sets a configuration option in raw format. This isn't a server setting. | + +### QueryResponse {#queryresponse} + +Response object that holds result of query execution. It is only available if the client got a response from a server. + + +This object should be closed as soon as possible to release a connection because the connection can't be re-used until all data of previous response is fully read. + + +| Method | Description | +|-------------------------------------|------------------------------------------------------------------------------------------------------| +| `ClickHouseFormat getFormat()` | Returns a format in which data in the response is encoded. | +| `InputStream getInputStream()` | Returns uncompressed byte stream of data in the specified format. | +| `OperationMetrics getMetrics()` | Returns object with operation metrics. | +| `String getQueryId()` | Returns query ID assigned for the operation by the application (through operation settings or by server). | +| `TimeZone getTimeZone()` | Returns timezone that should be used for handling Date/DateTime types in the response. | + +### Examples {#examples} + +- Example code is available in [repo](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/client-v2) +- Reference Spring Service [implementation](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/demo-service) + +## Common API {#common-api} + +### getTableSchema(String table) {#gettableschemastring-table} + +Fetches table schema for the `table`. + +**Signatures** + +```java +TableSchema getTableSchema(String table) +TableSchema getTableSchema(String table, String database) +``` + +**Parameters** + +`table` - table name for which schema data should be fetched. + +`database` - database where the target table is defined. + +**Return value** + +Returns a `TableSchema` object with list of table columns. + +### getTableSchemaFromQuery(String sql) {#gettableschemafromquerystring-sql} + +Fetches schema from a SQL statement. + +**Signatures** + +```java +TableSchema getTableSchemaFromQuery(String sql) +``` + +**Parameters** + +`sql` - "SELECT" SQL statement which schema should be returned. + +**Return value** + +Returns a `TableSchema` object with columns matching the `sql` expression. + +### TableSchema {#tableschema} + +### register(Class<?> clazz, TableSchema schema) {#registerclasslt-clazz-tableschema-schema} + +Compiles serialization and deserialization layer for the Java Class to use for writing/reading data with `schema`. The method will create a serializer and deserializer for the pair getter/setter and corresponding column. +Column match is found by extracting its name from a method name. For example, `getFirstName` will be for the column `first_name` or `firstname`. + +**Signatures** + +```java +void register(Class clazz, TableSchema schema) +``` + +**Parameters** + +`clazz` - Class representing the POJO used to read/write data. + +`schema` - Data schema to use for matching with POJO properties. + +**Examples** + +```java showLineNumbers +client.register(ArticleViewEvent.class, client.getTableSchema(TABLE_NAME)); +``` + +## Usage Examples {#usage-examples} + +Complete examples code is stored in the repo in a 'example` [folder](https://github.com/ClickHouse/clickhouse-java/tree/main/examples): + +- [client-v2](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/client-v2) - main set of examples. +- [demo-service](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/demo-service) - example of how to use the client in a Spring Boot application. +- [demo-kotlin-service](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/demo-kotlin-service) - example of how to use the client in Ktor (Kotlin) application. + +## Reading Data {#reading-data} + +There are two common ways to read data: + +- `query()` method that returns low-level `QueryResponse` object that contains `InputStream` with data. Usually combined with `ClickHouseBinaryFormatReader` for streaming reads but +can be used with any other custom reader implementation. `QueryResponse` also provides access to the result set metadata and metrics. +- `queryAll()` method and using `GenericRecord` for convenient row access. In this case the whole result set is loaded into memory. +- `queryRecords()` method that returns `com.clickhouse.client.api.query.Records` - an iterator for `GenericRecord` objects. This method uses streaming approach +(no data is loaded into memory) and utilises `GenericRecord` to access data. + +**Note:** streaming approach requires fast read otherwise it may cause server write timeout because data is read directly from the network stream. + +### Reading Arrays {#reading-arrays} + +**`ClickHouseBinaryFormatReader` Methods** + +- `getList(...)` - reads any `Array(...)` as `List`. Good default for flexible typed reads. Supports nested arrays. +- `getByteArray(...)`, `getShortArray(...)`, `getIntArray(...)`, `getLongArray(...)`, `getFloatArray(...)`, `getDoubleArray(...)`, `getBooleanArray(...)` - best for 1D arrays of primitive-compatible values. +- `getStringArray(...)` - for `Array(String)` (and enum values represented as names). +- `getObjectArray(...)` - generic option for any `Array(...)` element type, including nested arrays. Use to read arrays with nullable values and nested arrays. + +Index-based and name-based overloads are available for all methods. Index is 1-based. Index-based do dirrect access to a column. +Name-based methods require index lookup each time. + +```java +try (QueryResponse response = client.query("SELECT * FROM my_table").get()) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + while (reader.next() != null) { + + Object[] uint64 = reader.getObjectArray("uint64_arr"); // Array(UInt64) -> BigInteger[] + Object[] arr2d = reader.getObjectArray("arr2d"); // Array(Array(Int64)) -> Object[] + + // nested arrays are returned as nested Object[]: + Object[] firstInner = (Object[]) arr2d[0]; + Long firstValue = (Long) firstInner[0]; + } +} +``` + +**`GenericRecord` Methods** + +- `getList(...)` - reads any `Array(...)` as `List`. Good default for flexible typed reads. Supports nested arrays. +- `getByteArray(...)`, `getShortArray(...)`, `getIntArray(...)`, `getLongArray(...)`, `getFloatArray(...)`, `getDoubleArray(...)`, `getBooleanArray(...)` - best for 1D arrays of primitive-compatible values. +- `getStringArray(...)` - for `Array(String)` (and enum values represented as names). +- `getObjectArray(...)` - generic option for any `Array(...)` element type, including nested arrays. Use to read arrays with nullable values and nested arrays. + +Index-based and name-based overloads are available for all methods. Index is 1-based. Index-based do dirrect access to a column. +Name-based methods require index lookup each time. + +```java +try (QueryResponse response = client.query("SELECT * FROM my_table").get()) { + List rows = client.queryAll( + "SELECT int_arr, arr2d_nullable FROM test_arrays ORDER BY id"); + + for (GenericRecord row : rows) { + Object[] intArr = row.getObjectArray("int_arr"); // Array(Int32) -> Integer[] + Object[] arr2d = row.getObjectArray("arr2d_nullable"); // Array(Array(Nullable(Int32))) + + Object[] inner = (Object[]) arr2d[0]; + Object maybeNull = inner[1]; // may be null + } +} +``` + +## Migration Guide {#migration_guide} + +Old client (V1) was using `com.clickhouse.client.ClickHouseClient#builder` as start point. The new client (V2) uses similar pattern with `com.clickhouse.client.api.Client.Builder`. Main +differences are: +- no service loader is used to grab implementation. The `com.clickhouse.client.api.Client` is facade class for all kinds of implementation in the future. +- a fewer sources of configuration: one is provided to the builder and one is with operation settings (`QuerySettings`, `InsertSettings`). Previous version had configuration per node and was loading +env. variables in some cases. + +### Configuration Parameters Match {#migration_from_v1_config} + +There are 3 enum classes related to configuration in V1: +- `com.clickhouse.client.config.ClickHouseDefaults` - configuration parameters that supposed to be set in most use cases. Like `USER` and `PASSWORD`. +- `com.clickhouse.client.config.ClickHouseClientOption` - configuration parameters specific for the client. Like `HEALTH_CHECK_INTERVAL`. +- `com.clickhouse.client.http.config.ClickHouseHttpOption` - configuration parameters specific for HTTP interface. Like `RECEIVE_QUERY_PROGRESS`. + +They were designed to group parameters and provide clear separation. However in some cases it lead to a confusion (is there a difference between `com.clickhouse.client.config.ClickHouseDefaults#ASYNC` and +`com.clickhouse.client.config.ClickHouseClientOption#ASYNC`). The new V2 client uses `com.clickhouse.client.api.Client.Builder` as single dictionary of all possible client configuration options.There is +`com.clickhouse.client.api.ClientConfigProperties` where all configuration parameter names are listed. + +Table below shows what old options are supported in the new client and their new meaning. + +**Legend:** ✔ = supported, ✗ = dropped + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#HOST` | `Client.Builder#addEndpoint` | | +| `ClickHouseDefaults#PROTOCOL` | ✗ | Only HTTP supported in V2 | +| `ClickHouseDefaults#DATABASE`
`ClickHouseClientOption#DATABASE` | `Client.Builder#setDefaultDatabase` | | +| `ClickHouseDefaults#USER` | `Client.Builder#setUsername` | | +| `ClickHouseDefaults#PASSWORD` | `Client.Builder#setPassword` | | +| `ClickHouseClientOption#CONNECTION_TIMEOUT` | `Client.Builder#setConnectTimeout` | | +| `ClickHouseClientOption#CONNECTION_TTL` | `Client.Builder#setConnectionTTL` | | +| `ClickHouseHttpOption#MAX_OPEN_CONNECTIONS` | `Client.Builder#setMaxConnections` | | +| `ClickHouseHttpOption#KEEP_ALIVE`
`ClickHouseHttpOption#KEEP_ALIVE_TIMEOUT` | `Client.Builder#setKeepAliveTimeout` | | +| `ClickHouseHttpOption#CONNECTION_REUSE_STRATEGY` | `Client.Builder#setConnectionReuseStrategy` | | +| `ClickHouseHttpOption#USE_BASIC_AUTHENTICATION` | `Client.Builder#useHTTPBasicAuth` | | + +
+ + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#SSL_CERTIFICATE_TYPE` | ✗ | | +| `ClickHouseDefaults#SSL_KEY_ALGORITHM` | ✗ | | +| `ClickHouseDefaults#SSL_PROTOCOL` | ✗ | | +| `ClickHouseClientOption#SSL` | ✗ | See `Client.Builder#addEndpoint` | +| `ClickHouseClientOption#SSL_MODE` | ✗ | | +| `ClickHouseClientOption#SSL_ROOT_CERTIFICATE` | `Client.Builder#setRootCertificate` | SSL Auth should be enabled by `useSSLAuthentication` | +| `ClickHouseClientOption#SSL_CERTIFICATE` | `Client.Builder#setClientCertificate` | | +| `ClickHouseClientOption#SSL_KEY` | `Client.Builder#setClientKey` | | +| `ClickHouseClientOption#KEY_STORE_TYPE` | `Client.Builder#setSSLTrustStoreType` | | +| `ClickHouseClientOption#TRUST_STORE` | `Client.Builder#setSSLTrustStore` | | +| `ClickHouseClientOption#KEY_STORE_PASSWORD` | `Client.Builder#setSSLTrustStorePassword` | | +| `ClickHouseClientOption#SSL_SOCKET_SNI` | `Client.Builder#sslSocketSNI` | | +| `ClickHouseClientOption#CUSTOM_SOCKET_FACTORY` | ✗ | | +| `ClickHouseClientOption#CUSTOM_SOCKET_FACTORY_OPTIONS` | ✗ | See `Client.Builder#sslSocketSNI` to set SNI | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#SOCKET_TIMEOUT` | `Client.Builder#setSocketTimeout` | | +| `ClickHouseClientOption#SOCKET_REUSEADDR` | `Client.Builder#setSocketReuseAddress` | | +| `ClickHouseClientOption#SOCKET_KEEPALIVE` | `Client.Builder#setSocketKeepAlive` | | +| `ClickHouseClientOption#SOCKET_LINGER` | `Client.Builder#setSocketLinger` | | +| `ClickHouseClientOption#SOCKET_IP_TOS` | ✗ | | +| `ClickHouseClientOption#SOCKET_TCP_NODELAY` | `Client.Builder#setSocketTcpNodelay` | | +| `ClickHouseClientOption#SOCKET_RCVBUF` | `Client.Builder#setSocketRcvbuf` | | +| `ClickHouseClientOption#SOCKET_SNDBUF` | `Client.Builder#setSocketSndbuf` | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#COMPRESS` | `Client.Builder#compressServerResponse` | See also `useHttpCompression` | +| `ClickHouseClientOption#DECOMPRESS` | `Client.Builder#compressClientRequest` | See also `useHttpCompression` | +| `ClickHouseClientOption#COMPRESS_ALGORITHM` | ✗ | `LZ4` for non-http. Http uses `Accept-Encoding` | +| `ClickHouseClientOption#DECOMPRESS_ALGORITHM` | ✗ | `LZ4` for non-http. Http uses `Content-Encoding` | +| `ClickHouseClientOption#COMPRESS_LEVEL` | ✗ | | +| `ClickHouseClientOption#DECOMPRESS_LEVEL` | ✗ | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#PROXY_TYPE` | `Client.Builder#addProxy` | | +| `ClickHouseClientOption#PROXY_HOST` | `Client.Builder#addProxy` | | +| `ClickHouseClientOption#PROXY_PORT` | `Client.Builder#addProxy` | | +| `ClickHouseClientOption#PROXY_USERNAME` | `Client.Builder#setProxyCredentials` | | +| `ClickHouseClientOption#PROXY_PASSWORD` | `Client.Builder#setProxyCredentials` | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#MAX_EXECUTION_TIME` | `Client.Builder#setExecutionTimeout` | | +| `ClickHouseClientOption#RETRY` | `Client.Builder#setMaxRetries` | See also `retryOnFailures` | +| `ClickHouseHttpOption#AHC_RETRY_ON_FAILURE` | `Client.Builder#retryOnFailures` | | +| `ClickHouseClientOption#FAILOVER` | ✗ | | +| `ClickHouseClientOption#REPEAT_ON_SESSION_LOCK` | ✗ | | +| `ClickHouseClientOption#SESSION_ID` | ✗ | | +| `ClickHouseClientOption#SESSION_CHECK` | ✗ | | +| `ClickHouseClientOption#SESSION_TIMEOUT` | ✗ | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#SERVER_TIME_ZONE`
`ClickHouseClientOption#SERVER_TIME_ZONE` | `Client.Builder#setServerTimeZone` | | +| `ClickHouseClientOption#USE_SERVER_TIME_ZONE` | `Client.Builder#useServerTimeZone` | | +| `ClickHouseClientOption#USE_SERVER_TIME_ZONE_FOR_DATES` | | | +| `ClickHouseClientOption#USE_TIME_ZONE` | `Client.Builder#useTimeZone` | | + +
+ + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#BUFFER_SIZE` | `Client.Builder#setClientNetworkBufferSize` | | +| `ClickHouseClientOption#BUFFER_QUEUE_VARIATION` | ✗ | | +| `ClickHouseClientOption#READ_BUFFER_SIZE` | ✗ | | +| `ClickHouseClientOption#WRITE_BUFFER_SIZE` | ✗ | | +| `ClickHouseClientOption#REQUEST_CHUNK_SIZE` | ✗ | | +| `ClickHouseClientOption#REQUEST_BUFFERING` | ✗ | | +| `ClickHouseClientOption#RESPONSE_BUFFERING` | ✗ | | +| `ClickHouseClientOption#MAX_BUFFER_SIZE` | ✗ | | +| `ClickHouseClientOption#MAX_QUEUED_BUFFERS` | ✗ | | +| `ClickHouseClientOption#MAX_QUEUED_REQUESTS` | ✗ | | +| `ClickHouseClientOption#REUSE_VALUE_WRAPPER` | ✗ | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#ASYNC`
`ClickHouseClientOption#ASYNC` | `Client.Builder#useAsyncRequests` | | +| `ClickHouseDefaults#MAX_SCHEDULER_THREADS` | ✗ | see `setSharedOperationExecutor` | +| `ClickHouseDefaults#MAX_THREADS` | ✗ | see `setSharedOperationExecutor` | +| `ClickHouseDefaults#THREAD_KEEPALIVE_TIMEOUT` | see `setSharedOperationExecutor` | | +| `ClickHouseClientOption#MAX_THREADS_PER_CLIENT` | ✗ | | +| `ClickHouseClientOption#MAX_CORE_THREAD_TTL` | ✗ | | + +
+ + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseHttpOption#CUSTOM_HEADERS` | `Client.Builder#httpHeaders` | | +| `ClickHouseHttpOption#CUSTOM_PARAMS` | ✗ | See `Client.Builder#serverSetting` | +| `ClickHouseClientOption#CLIENT_NAME` | `Client.Builder#setClientName` | | +| `ClickHouseHttpOption#CONNECTION_PROVIDER` | ✗ | | +| `ClickHouseHttpOption#DEFAULT_RESPONSE` | ✗ | | +| `ClickHouseHttpOption#SEND_HTTP_CLIENT_ID` | ✗ | | +| `ClickHouseHttpOption#AHC_VALIDATE_AFTER_INACTIVITY` | ✗ | Always enabled when Apache Http Client is used | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#FORMAT`
`ClickHouseClientOption#FORMAT` | ✗ | Moved to operation settings (`QuerySettings` and `InsertSettings`) | +| `ClickHouseClientOption#QUERY_ID` | ✗ | See `QuerySettings` and `InsertSettings` | +| `ClickHouseClientOption#LOG_LEADING_COMMENT` | ✗ | See `QuerySettings#logComment` and `InsertSettings#logComment` | +| `ClickHouseClientOption#MAX_RESULT_ROWS` | ✗ | Is server side setting | +| `ClickHouseClientOption#RESULT_OVERFLOW_MODE` | ✗ | Is server side setting | +| `ClickHouseHttpOption#RECEIVE_QUERY_PROGRESS` | ✗ | Server side setting | +| `ClickHouseHttpOption#WAIT_END_OF_QUERY` | ✗ | Server side setting | +| `ClickHouseHttpOption#REMEMBER_LAST_SET_ROLES` | `Client#setDBRoles` | Runtime config now. See also `QuerySettings#setDBRoles` and `InsertSettings#setDBRoles` | + +
+ + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseClientOption#AUTO_DISCOVERY` | ✗ | | +| `ClickHouseClientOption#LOAD_BALANCING_POLICY` | ✗ | | +| `ClickHouseClientOption#LOAD_BALANCING_TAGS` | ✗ | | +| `ClickHouseClientOption#HEALTH_CHECK_INTERVAL` | ✗ | | +| `ClickHouseClientOption#HEALTH_CHECK_METHOD` | ✗ | | +| `ClickHouseClientOption#NODE_DISCOVERY_INTERVAL` | ✗ | | +| `ClickHouseClientOption#NODE_DISCOVERY_LIMIT` | ✗ | | +| `ClickHouseClientOption#NODE_CHECK_INTERVAL` | ✗ | | +| `ClickHouseClientOption#NODE_GROUP_SIZE` | ✗ | | +| `ClickHouseClientOption#CHECK_ALL_NODES` | ✗ | | + + + + + +| V1 Configuration | V2 Builder Method | Comments | +|------------------|-------------------|----------| +| `ClickHouseDefaults#AUTO_SESSION` | ✗ | Session support will be reviewed | +| `ClickHouseDefaults#BUFFERING` | ✗ | | +| `ClickHouseDefaults#MAX_REQUESTS` | ✗ | | +| `ClickHouseDefaults#ROUNDING_MODE` | | | +| `ClickHouseDefaults#SERVER_VERSION`
`ClickHouseClientOption#SERVER_VERSION` | `Client.Builder#setServerVersion` | | +| `ClickHouseDefaults#SRV_RESOLVE` | ✗ | | +| `ClickHouseClientOption#CUSTOM_SETTINGS` | | | +| `ClickHouseClientOption#PRODUCT_NAME` | ✗ | Use client name | +| `ClickHouseClientOption#RENAME_RESPONSE_COLUMN` | ✗ | | +| `ClickHouseClientOption#SERVER_REVISION` | ✗ | | +| `ClickHouseClientOption#TRANSACTION_TIMEOUT` | ✗ | | +| `ClickHouseClientOption#WIDEN_UNSIGNED_TYPES` | ✗ | | +| `ClickHouseClientOption#USE_BINARY_STRING` | ✗ | | +| `ClickHouseClientOption#USE_BLOCKING_QUEUE` | ✗ | | +| `ClickHouseClientOption#USE_COMPILATION` | ✗ | | +| `ClickHouseClientOption#USE_OBJECTS_IN_ARRAYS` | ✗ | | +| `ClickHouseClientOption#MAX_MAPPER_CACHE` | ✗ | | +| `ClickHouseClientOption#MEASURE_REQUEST_TIME` | ✗ | | + +
+
+ +### General Differences + +- Client V2 uses less proprietary classes to increase portability. For example, V2 works with any implementation of `java.io.InputStream` for +writing data to a server. +- Client V2 `async` settings is `off` by default. It means no extra threads and more application control over client. This setting should be `off` for majority of use cases. Enabling `async` will create a separate thread for a request. It only make sense when using application controlled +executor (see `com.clickhouse.client.api.Client.Builder#setSharedOperationExecutor`) + +### Writing Data + +- use any implementation of `java.io.InputStream`. V1 `com.clickhouse.data.ClickHouseInputStream` is supported but NOT recommended. +- once end of input stream is detected it handled accordingly. Previously output stream of a request should be closed. + +__V1 Insert TSV formatted data.__ +```java +InputStream inData = getInData(); +ClickHouseRequest.Mutation request = client.read(server) + .write() + .table(tableName) + .format(ClickHouseFormat.TSV); +ClickHouseConfig config = request.getConfig(); +CompletableFuture future; +try (ClickHousePipedOutputStream requestBody = ClickHouseDataStreamFactory.getInstance() + .createPipedOutputStream(config)) { + // start the worker thread which transfer data from the input into ClickHouse + future = request.data(requestBody.getInputStream()).execute(); + + // Copy data from inData stream to requestBody stream + + // We need to close the stream before getting a response + requestBody.close(); + + try (ClickHouseResponse response = future.get()) { + ClickHouseResponseSummary summary = response.getSummary(); + Assert.assertEquals(summary.getWrittenRows(), numRows, "Num of written rows"); + } +} + +``` + +__V2 Insert TSV formatted data.__ + +```java +InputStream inData = getInData(); +InsertSettings settings = new InsertSettings().setInputStreamCopyBufferSize(8198 * 2); // set copy buffer size +try (InsertResponse response = client.insert(tableName, inData, ClickHouseFormat.TSV, settings).get(30, TimeUnit.SECONDS)) { + + // Insert is complete at this point + +} catch (Exception e) { + // Handle exception +} +``` +- there is a single method to call. No need to create an additional request object. +- request body stream is closed automatically when all data is copied. +- new low-level API is available `com.clickhouse.client.api.Client#insert(java.lang.String, java.util.List, com.clickhouse.client.api.DataStreamWriter, com.clickhouse.data.ClickHouseFormat, com.clickhouse.client.api.insert.InsertSettings)`. `com.clickhouse.client.api.DataStreamWriter` is designed to implement custom data writing logic. For instance, reading data from a +queue. + +### Reading Data + +- Data is read in `RowBinaryWithNamesAndTypes` format by default. Currently only this format is supported when data binding is required. +- Data can be read as a collection of records using `List com.clickhouse.client.api.Client#queryAll(java.lang.String)` method. It will read data to a memory and release connection. No need for extra handling. `GenericRecord` gives access to data, implements some conversions. + +```java +Collection records = client.queryAll("SELECT * FROM table"); +for (GenericRecord record : records) { + int rowId = record.getInteger("rowID"); + String name = record.getString("name"); + LocalDateTime ts = record.getLocalDateTime("ts"); +} + +``` + +
+ + + +Java client library to communicate with a DB server through its protocols. Current implementation supports only [HTTP interface](/concepts/features/interfaces/http). The library provides own API to send requests to a server. + + +**Deprecation** + +This library will be deprecated soon. Use the latest [Java Client](/integrations/language-clients/java/client) for new projects + + +## Setup {#v1-setup} + + + + +```xml + + + com.clickhouse + clickhouse-http-client + 0.7.2 + +``` + + + + +```kotlin +// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-http-client +implementation("com.clickhouse:clickhouse-http-client:0.7.2") +``` + + + +```groovy +// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-http-client +implementation 'com.clickhouse:clickhouse-http-client:0.7.2' +``` + + + + +Since version `0.5.0`, the driver uses a new client http library that needs to be added as a dependency. + + + + +```xml + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + +``` + + + + +```kotlin +// https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 +implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1") +``` + + + +```groovy +// https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 +implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' +``` + + + + +## Initialization {#v1-initialization} + +Connection URL Format: `protocol://host[:port][/database][?param[=value][¶m[=value]][#tag[,tag]]`, for example: + +- `http://localhost:8443?ssl=true&sslmode=NONE` +- `https://(https://explorer@play.clickhouse.com:443` + +Connect to a single node: + +```java showLineNumbers +ClickHouseNode server = ClickHouseNode.of("http://localhost:8123/default?compress=0"); +``` +Connect to a cluster with multiple nodes: + +```java showLineNumbers +ClickHouseNodes servers = ClickHouseNodes.of( + "jdbc:ch:http://server1.domain,server2.domain,server3.domain/my_db" + + "?load_balancing_policy=random&health_check_interval=5000&failover=2"); +``` + +## Query API {#v1-query-api} + +```java showLineNumbers +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * from numbers limit :limit") + .params(1000) + .executeAndWait()) { + ClickHouseResponseSummary summary = response.getSummary(); + long totalRows = summary.getTotalRowsToRead(); +} +``` + +## Streaming Query API {#v1-streaming-query-api} + +```java showLineNumbers +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * from numbers limit :limit") + .params(1000) + .executeAndWait()) { + for (ClickHouseRecord r : response.records()) { + int num = r.getValue(0).asInteger(); + // type conversion + String str = r.getValue(0).asString(); + LocalDate date = r.getValue(0).asDate(); + } +} +``` + +See [complete code example](https://github.com/ClickHouse/clickhouse-java/blob/main/examples/client/src/main/java/com/clickhouse/examples/jdbc/Main.java#L73) in the [repo](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/client). + +## Insert API {#v1-insert-api} + +```java showLineNumbers + +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers).write() + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("insert into my_table select c2, c3 from input('c1 UInt8, c2 String, c3 Int32')") + .data(myInputStream) // `myInputStream` is source of data in RowBinary format + .executeAndWait()) { + ClickHouseResponseSummary summary = response.getSummary(); + summary.getWrittenRows(); +} +``` + +See [complete code example](https://github.com/ClickHouse/clickhouse-java/blob/main/examples/client/src/main/java/com/clickhouse/examples/jdbc/Main.java#L39) in the [repo](https://github.com/ClickHouse/clickhouse-java/tree/main/examples/client). + +**RowBinary Encoding** + +RowBinary format is described on its [page](/reference/formats/RowBinary/RowBinaryWithNamesAndTypes). + +There is an example of [code](https://github.com/ClickHouse/clickhouse-kafka-connect/blob/main/src/main/java/com/clickhouse/kafka/connect/sink/db/ClickHouseWriter.java#L622). + +## Features {#v1-features} +### Compression {#v1-compression} + +The client will by default use LZ4 compression, which requires this dependency: + + + + +```xml + + + org.lz4 + lz4-java + 1.8.0 + +``` + + + + +```kotlin +// https://mvnrepository.com/artifact/org.lz4/lz4-java +implementation("org.lz4:lz4-java:1.8.0") +``` + + + +```groovy +// https://mvnrepository.com/artifact/org.lz4/lz4-java +implementation 'org.lz4:lz4-java:1.8.0' +``` + + + + +You can choose to use gzip instead by setting `compress_algorithm=gzip` in the connection URL. + +Alternatively, you can disable compression a few ways. + +1. Disable by setting `compress=0` in the connection URL: `http://localhost:8123/default?compress=0` +2. Disable via the client configuration: + +```java showLineNumbers +ClickHouseClient client = ClickHouseClient.builder() + .config(new ClickHouseConfig(Map.of(ClickHouseClientOption.COMPRESS, false))) + .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) + .build(); +``` + +See the [compression documentation](/guides/clickhouse/data-modelling/compression/compression-modes) to learn more about different compression options. + +### Multiple queries {#v1-multiple-queries} + +Execute multiple queries in a worker thread one after another within same session: + +```java showLineNumbers +CompletableFuture> future = ClickHouseClient.send(servers.apply(servers.getNodeSelector()), + "create database if not exists my_base", + "use my_base", + "create table if not exists test_table(s String) engine=Memory", + "insert into test_table values('1')('2')('3')", + "select * from test_table limit 1", + "truncate table test_table", + "drop table if exists test_table"); +List results = future.get(); +``` + +### Named Parameters {#v1-named-parameters} + +You can pass parameters by name rather than relying solely on their position in the parameter list. This capability is available using `params` function. + +```java showLineNumbers +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * from my_table where name=:name limit :limit") + .params("Ben", 1000) + .executeAndWait()) { + //... + } +} +``` + + +**Parameters** + +All `params` signatures involving `String` type (`String`, `String[]`, `Map`) assume the keys being passed are valid ClickHouse SQL strings. For instance: + +```java showLineNumbers +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * from my_table where name=:name") + .params(Map.of("name","'Ben'")) + .executeAndWait()) { + //... + } +} +``` + +If you prefer not to parse String objects to ClickHouse SQL manually, you can use the helper function `ClickHouseValues.convertToSqlExpression` located at `com.clickhouse.data`: + +```java showLineNumbers +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP); + ClickHouseResponse response = client.read(servers) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query("select * from my_table where name=:name") + .params(Map.of("name", ClickHouseValues.convertToSqlExpression("Ben's"))) + .executeAndWait()) { + //... + } +} +``` + +In the example above, `ClickHouseValues.convertToSqlExpression` will escape the inner single quote, and surround the variable with a valid single quotes. + +Other types, such as `Integer`, `UUID`, `Array` and `Enum` will be converted automatically inside `params`. + + +## Node Discovery {#v1-node-discovery} + +Java client provides the ability to discover ClickHouse nodes automatically. Auto-discovery is disabled by default. To manually enable it, set `auto_discovery` to `true`: + +```java +properties.setProperty("auto_discovery", "true"); +``` + +Or in the connection URL: + +```plaintext +jdbc:ch://my-server/system?auto_discovery=true +``` + +If auto-discovery is enabled, there is no need to specify all ClickHouse nodes in the connection URL. Nodes specified in the URL will be treated as seeds, and the Java client will automatically discover more nodes from system tables and/or clickhouse-keeper or zookeeper. + +The following options are responsible for auto-discovery configuration: + +| Property | Default | Description | +|-------------------------|---------|-------------------------------------------------------------------------------------------------------| +| auto_discovery | `false` | Whether the client should discover more nodes from system tables and/or clickhouse-keeper/zookeeper. | +| node_discovery_interval | `0` | Node discovery interval in milliseconds, zero or negative value means one-time discovery. | +| node_discovery_limit | `100` | Maximum number of nodes that can be discovered at a time; zero or negative value means no limit. | + +### Load Balancing {#v1-load-balancing} + +The Java client chooses a ClickHouse node to send requests to, according to the load-balancing policy. In general, the load-balancing policy is responsible for the following things: + +1. Get a node from a managed node list. +2. Managing node's status. +3. Optionally schedule a background process for node discovery (if auto-discovery is enabled) and run a health check. + +Here is a list of options to configure load balancing: + +| Property | Default | Description | +|-----------------------|-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| load_balancing_policy | `""` | The load-balancing policy can be one of:
  • `firstAlive` - request is sent to the first healthy node from the managed node list
  • `random` - request is sent to a random node from the managed node list
  • `roundRobin` - request is sent to each node from the managed node list, in turn.
  • full qualified class name implementing `ClickHouseLoadBalancingPolicy` - custom load balancing policy
  • If it isn't specified the request is sent to the first node from the managed node list | +| load_balancing_tags | `""` | Load balancing tags for filtering out nodes. Requests are sent only to nodes that have the specified tags | +| health_check_interval | `0` | Health check interval in milliseconds, zero or negative value means one-time. | +| health_check_method | `ClickHouseHealthCheckMethod.SELECT_ONE` | Health check method. Can be one of:
  • `ClickHouseHealthCheckMethod.SELECT_ONE` - check with `select 1` query
  • `ClickHouseHealthCheckMethod.PING` - protocol-specific check, which is generally faster
  • | +| node_check_interval | `0` | Node check interval in milliseconds, negative number is treated as zero. The node status is checked if the specified amount of time has passed since the last check.
    The difference between `health_check_interval` and `node_check_interval` is that the `health_check_interval` option schedules the background job, which checks the status for the list of nodes (all or faulty), but `node_check_interval` specifies the amount of time has passed since the last check for the particular node | +| check_all_nodes | `false` | Whether to perform a health check against all nodes or just faulty ones. | + +### Failover and retry {#v1-failover-and-retry} + +Java client provides configuration options to set up failover and retry behavior for failed queries: + +| Property | Default | Description | +|-------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| failover | `0` | Maximum number of times a failover can happen for a request. Zero or a negative value means no failover. Failover sends the failed request to a different node (according to the load-balancing policy) in order to recover from failover. | +| retry | `0` | Maximum number of times retry can happen for a request. Zero or a negative value means no retry. Retry sends a request to the same node and only if the ClickHouse server returns the `NETWORK_ERROR` error code | +| repeat_on_session_lock | `true` | Whether to repeat execution when the session is locked until timed out(according to `session_timeout` or `connect_timeout`). The failed request is repeated if the ClickHouse server returns the `SESSION_IS_LOCKED` error code | + +### Adding custom http headers {#v1-adding-custom-http-headers} + +Java client support HTTP/S transport layer in case we want to add custom HTTP headers to the request. +We should use the custom_http_headers property, and the headers need to be `,` separated. The header key/value should be divided using `=` + +## Java Client support {#v1-java-client-support} + +```java +options.put("custom_http_headers", "X-ClickHouse-Quota=test, X-ClickHouse-Test=test"); +``` + +## JDBC Driver {#v1-jdbc-driver} + +```java +properties.setProperty("custom_http_headers", "X-ClickHouse-Quota=test, X-ClickHouse-Test=test"); +``` + +
    diff --git a/docs/clickhouse-docs/date-time-guide.mdx b/docs/clickhouse-docs/date-time-guide.mdx new file mode 100644 index 000000000..57b15c1d2 --- /dev/null +++ b/docs/clickhouse-docs/date-time-guide.mdx @@ -0,0 +1,198 @@ +--- +sidebarTitle: 'Working with Date/Time values in JDBC' +keywords: ['java', 'jdbc', 'driver', 'integrate', 'guide', 'Date', 'Time'] +description: 'Guide to using Date/Time values in JDBC' +slug: /integrations/language-clients/java/jdbc_date_time_guide +title: 'Date/Time values guide' +doc_type: 'guide' +integration: + - support_level: 'core' + - category: 'language_client' +--- + +Date, Time and Timestamp require attention because there are several common problems related to them. +The most common problem is how to handle time zones. Another problem is string representation and how to use it. +Besides that, every database and driver has its own specifics and limitations. + +This document aims to be a decision-making guide by describing tasks, giving implementation details and explaining problems. + +## Timezones {#timezones} + +We all know that timezones are hard to handle (daylight saving time, constant offset changes). But this section is about another problem linked to timezones: how they relate to timestamp string representation. + +### How ClickHouse converts DateTime strings {#clickhouse-datetime-string-conversion} + +ClickHouse uses the following rules to convert `DateTime` string values: + +- If a column is defined with a timezone (`DateTime64(9, ‘Asia/Tokyo’)`), then the string value will be treated as a timestamp in that timezone. `2026-01-01 13:00:00` will be `2026-01-01 04:00:00` in `UTC` time. +- If a column has no timezone definition, then only the server timezone is used. Important: the `session_timezone` setting has no effect. So if the server timezone is `UTC` and the session timezone is `America/Los_Angeles`, then `2026-01-01 13:00:00` will be written as `UTC` time. +- When a value is read from a column without a timezone definition, the `session_timezone` is used, or if not set, the server timezone. That is why reading timestamps as strings can be affected by `session_timezone`. There is nothing wrong with this, but it should be kept in mind. + +### Writing timestamps across timezones {#writing-timestamps-across-timezones} + +Now let’s assume we have an application running in the `us-west` region with local timezone `UTC-8`, and we need to write a local timestamp `2026-01-01 02:00:00` which in `UTC` is `2026-01-01 10:00:00`: + +- Writing it as a string requires converting it to the server timezone or column timezone. +- Writing it as a language-native time structure requires the driver to know the target timezone, but: + - It is not always possible + - The driver API is not well-designed for this + - The only way is to describe what transformations will be performed so the application can compensate (or write a Unix timestamp as a number) + +### Java and JDBC timestamp APIs {#java-and-jdbc-timestamp-apis} + +Java and JDBC have different ways to set a timestamp: + +1. Use the `Timestamp` class, which is really a Unix timestamp. + 1. When used with a `Calendar` object, it makes it possible to reinterpret the `Timestamp` in the calendar’s timezone. + 2. `Timestamp` has an internal calendar that is not very obvious. +2. Use the `LocalDateTime` class, which is easy to convert to any timezone, but there is no method allowing you to pass a target timezone. +3. Use the `ZonedDateTime` class, which helps with timezone conversion when writing to a `DateTime` without a timezone (because we know to use the server timezone). + 1. But writing a `ZonedDateTime` to a column with a defined timezone requires the user to compensate for the driver conversion. +4. Use `Long` to write Unix timestamp milliseconds. +5. Use `String` to do all conversions on the application side (which is not very portable). + + +Prefer use of `java.time.ZoneId#of(java.lang.String)` when searching for a timezone by ID. +This method will throw an exception if the timezone is not found (`java.util.TimeZone#getTimeZone(java.lang.String)` will silently fall back to `GMT`). + +The correct way to get the `Tokyo` timezone is: + +`TimeZone.getTimeZone(ZoneId.of("Asia/Tokyo"))` + + +## Date {#date} + +Dates are timezone-agnostic by nature. There are `Date` and `Date32` types to store dates. Both types use a number of days since Epoch (1970-01-01). `Date` uses only positive numbers of days, so its range ends on `2149-06-06`. `Date32` handles negative numbers of days to cover dates before `1970-01-01`, but its range is smaller (from `1900-01-01` to `2100-01-01`, where 0 is `1970-01-01`). ClickHouse sees `2026-01-01` as `2026-01-01` in any timezone, and there is no timezone parameter for column definitions. + +### Using `java.time.LocalDate` {#using-localdate} + +In Java, the most suitable class to represent date values is `java.time.LocalDate`. The client uses this class to store the value of `Date` and `Date32` columns (reading `LocalDate.ofEpochDay((long)readUnsignedShortLE())`). + +We recommend using `java.time.LocalDate` because it is not affected by timezone transformations and is part of the modern time API. + +### Using `java.sql.Date` {#using-java-sql-date} + +`LocalDate` was introduced in Java 8. Before that, `java.sql.Date` was used to write/read dates. Internally this class is a wrapper around an instant (a time value representing an absolute point in time). Because of this, `toString()` returns a different date depending on what timezone the JVM is in. It requires the driver to carefully construct values and requires the user to be aware of this. + +### Calendar-based reinterpretation {#calendar-based-reinterpretation} + +`java.sql.ResultSet` has a method for getting date values that accepts a `Calendar`, and there is a similar method in `java.sql.PreparedStatement`. This was designed to let the JDBC driver reinterpret a date value in the specified timezone. For example, the DB has value `2026-01-01` but the application wants to see this date as midnight in `Tokyo`. That means the returned `java.sql.Date` object will get a specific instant, and when converted to the local timezone it may be a different date because of the time difference. We can achieve the same with `LocalDate` by using `java.time.LocalDate#atStartOfDay(java.time.ZoneId)`. + +The ClickHouse JDBC driver always returns a `java.sql.Date` object that points to the **local** date at midnight. In other words, if the date is `2026-01-01`, we mean `2026-01-01 12:00 AM` in the JVM timezone (the same behavior as PostgreSQL and MariaDB JDBC drivers). + +## Time {#time} + +Time values, like Date values, are timezone-agnostic in most cases. ClickHouse does no transformations of time literal values to any timezone — `’6:30’` is the same wherever it is read. + +### ClickHouse Time types {#clickhouse-time-types} + +`Time` and `Time64` were introduced in `25.6`. Before that, the timestamp types `DateTime` and `DateTime64` were used instead (discussed later in this guide). `Time` is stored as a 32-bit integer number of seconds and is in the range `[-999:59:59, 999:59:59]`. `Time64` is encoded as an unsigned Decimal64 and stores different time units depending on precision. Common choices are 3 (milliseconds), 6 (microseconds), and 9 (nanoseconds). The precision value range is `[0, 9]`. + +### Java type mapping {#java-type-mapping} + +The client reads `Time` and `Time64` and stores them as `LocalDateTime`. This is done to support the negative time range (`LocalTime` doesn’t support it). In this case, the date part is the Epoch date `1970-01-01`, so negative values will be before this date. + +The main support for time types is implemented using `LocalTime` (when the value is within a day) and `Duration` to use the full range of values. `LocalDateTime` can be used for reading only. + +### Using `java.sql.Time` {#using-java-sql-time} + +Using `java.sql.Time` is limited to the `LocalTime` range. Internally, `java.sql.Time` is converted to a string literal. The value may be changed by using a Calendar parameter with `PreparedStatement#setTime()`. + +### The `toTime` function {#totime-function} + + +- `toTime` always requires `Date`, `DateTime`, or another similar type. It does not accept strings. Related issue: https://github.com/ClickHouse/ClickHouse/issues/89896 +- It is aliased to [`toTimeWithFixedDate`](/reference/functions/regular-functions/date-time-functions#toTimeWithFixedDate). +- There is a timezone-related issue: https://github.com/ClickHouse/ClickHouse/pull/90310 + + +## Timestamp {#timestamp} + +A timestamp is a specific point in time. For example, a Unix timestamp represents any point in time as a number of seconds relative to `1970-01-01 00:00:00` `UTC` (a negative number of seconds represents a timestamp before Unix time, and a positive number represents one after). This representation is easy to calculate and handle if the observer is in the `UTC` timezone or uses it over their local one. + +### ClickHouse Timestamp types {#clickhouse-timestamp-types} + +There are `DateTime` (32-bit integer, resolution is always seconds) and `DateTime64` (64-bit integer, resolution depends on definition) timestamp types in ClickHouse. Values are always stored as UTC timestamps. This means that when represented as numbers, no timezone conversion is applied. + +### String representation and timezone behavior {#string-representation-and-timezone-behavior} + +String representation has complexities: + +- If no timezone is specified in the column definition and a string is passed on write, it will be converted from the server timezone to a UTC timestamp number. When a value is read from such a column, it will be converted from a UTC timestamp to a literal timestamp using the server or session timezone (a similar approach is applied to timestamp literals in expressions where the timezone is not defined explicitly). +- If a timezone is specified in the column definition, then only that timezone is used in all string conversions. This contradicts the logic when no timezone is specified, so it requires a good understanding of how data is written for each column in the query. +- If a date is passed as a string in a format that includes a timezone, then a conversion function is needed. Usually [`parseDateTimeBestEffort`](/reference/functions/regular-functions/type-conversion-functions#parseDateTimeBestEffort) is used. + +### How the JDBC driver handles timestamps {#how-jdbc-driver-handles-timestamps} + +In the JDBC driver, we convert timestamps to a numeric representation: + +```java +"fromUnixTimestamp64Nano(" + epochSeconds * 1_000_000_000L + nanos + ")" +``` + +This representation solves most conversion issues with timestamp values because it sends data to the server in a unified format. However, this approach requires a small adjustment in SQL statements, but it provides the simplest and most straightforward way to write timestamps to any column. + +`DateTime` and `DateTime64` are read and stored on the client as `java.time.ZonedDateTime`, which helps convert such values to any other timezone (timezone information is preserved). + +### Common pitfall with `toDateTime64` {#common-pitfall-todatetime64} + +The following code example looks correct but fails on the assertion: + +```java +String sql = "SELECT toDateTime64(?, 3)"; +try (PreparedStatement stmt = conn.prepareStatement(sql)) { + LocalDateTime localTs = LocalDateTime.parse("2021-01-01T01:34:56"); + stmt.setObject(1, localTs); + try (ResultSet rs = stmt.executeQuery()) { + rs.next(); + assertEquals(rs.getObject(1, LocalDateTime.class), localTs); + } +} +``` + +This happens because `toDateTime64` uses the server timezone and doesn’t know about the source timezone. + +## Conversion tables {#conversion-tables} + +If a conversion pair is not mentioned in the tables below, then the conversion is not supported. For example, `Date` columns cannot be read as `java.sql.Timestamp` because there is no time part. +Driver doesn't convert integer values to any of date/time values. Calling `pstmt.setLong("timestamp", 1772132359L)` will result `1772132359` written as number to a server what will be treated as +UTC Unix timestamp in seconds. + +### Writing values with `PreparedStatement#setObject` {#writing-values-setobject} + +The following table shows how values are converted when set with `PreparedStatement#setObject(column, value)`: + +| Class of `value` | Conversion | +| --- | --- | +| `java.time.LocalDate` | Formatted as `YYYY-MM-DD`. | +| `java.sql.Date` | Converted with the default calendar and formatted as `LocalDate` (`YYYY-MM-DD`). | +| `java.time.LocalTime` | Formatted as `HH:mm:ss`. | +| `java.time.Duration` | Formatted as `HHH:mm:ss`. Value can be negative. | +| `java.sql.Time` | Converted with the default calendar and formatted as `LocalTime` (`HH:mm`). | +| `java.time.LocalDateTime` | Converted to Unix timestamp in nanoseconds and wrapped with `fromUnixTimestamp64Nano`. | +| `java.time.ZonedDateTime` | Converted to Unix timestamp in nanoseconds and wrapped with `fromUnixTimestamp64Nano`. | +| `java.sql.Timestamp` | Converted to Unix timestamp in nanoseconds and wrapped with `fromUnixTimestamp64Nano`. | + + +The type of the column should be considered unknown. It is up to the application to decide what to pass to the prepared statement. + + +### Reading values with `ResultSet#getObject` {#reading-values-getobject} + +The following table shows how values are converted when read with `ResultSet#getObject(column, class)`: + +| ClickHouse Data Type of `column` | Value of `class` | Conversion | +| --- | --- | --- | +| `Date` or `Date32` | `java.time.LocalDate` | DB value (number of days) converted to `LocalDate`. | +| `Date` or `Date32` | `java.sql.Date` | DB value (number of days) converted to `LocalDate` and then to `java.sql.Date` using local timezone midnight as the time part. If a calendar is used, its timezone will be used instead of the local one. Example: DB value `1970-01-10` → `LocalDate` is `1970-01-10`. | +| `Time` or `Time64` | `java.time.LocalTime` | DB value converted to `LocalDateTime` and then to `LocalTime`. This works only for time within a day. | +| `Time` or `Time64` | `java.time.LocalDateTime` | DB value converted to `LocalDateTime`. | +| `Time` or `Time64` | `java.sql.Time` | DB value converted to `LocalDateTime` and then to `java.sql.Time` using the default calendar. This works only for time within a day. | +| `Time` or `Time64` | `java.time.Duration` | DB value converted to `LocalDateTime` and then to `Duration`. | +| `DateTime` or `DateTime64` | `java.time.LocalDateTime` | DB value converted to `ZonedDateTime`, then to `LocalDateTime`. | +| `DateTime` or `DateTime64` | `java.time.ZonedDateTime` | DB value converted to `ZonedDateTime`. | +| `DateTime` or `DateTime64` | `java.sql.Timestamp` | DB value converted to `ZonedDateTime`, then to `java.sql.Timestamp` using the default timezone. | + +### Using Calendar-based methods {#using-calendar-based-methods} + +Use `ResultSet#getTime(column, calendar)` and `ResultSet#getDate(column, calendar)` if values were stored using `PreparedStatement#setTime(param, value, calendar)` and `PreparedStatement#setDate(param, value, calendar)` accordingly. diff --git a/docs/clickhouse-docs/index.mdx b/docs/clickhouse-docs/index.mdx new file mode 100644 index 000000000..14fe644da --- /dev/null +++ b/docs/clickhouse-docs/index.mdx @@ -0,0 +1,192 @@ +--- +title: 'Java clients overview' +keywords: ['clickhouse', 'java', 'jdbc', 'client', 'integrate', 'r2dbc'] +description: 'Options for connecting to ClickHouse from Java' +slug: /integrations/java +doc_type: 'reference' +integration: + - support_level: 'core' + - category: 'language_client' +--- + +- [Client 0.8+](/integrations/language-clients/java/client) +- [JDBC 0.8+](/integrations/language-clients/java/jdbc) +- [R2DBC Driver](/integrations/language-clients/java/r2dbc) + +## ClickHouse client {#clickhouse-client} + +Java client is a library implementing own API that abstracts details of network communications with ClickHouse server. Currently HTTP Interface is supported only. The library provide utilities to work with different ClickHouse formats and other related functions. + +Java Client was developed far back in 2015. Its codebase became very hard to maintain, API is confusing, it is hard to optimize it further. So we have refactored it in 2024 into a new component `client-v2`. It has clear API, lighter codebase and more performance improvements, better ClickHouse formats support (RowBinary & Native mainly). JDBC will use this client in near feature. + +### Supported data types {#supported-data-types} + +|**Data Type** |**Client V2 Support**|**Client V1 Support**| +|-----------------------|---------------------|---------------------| +|Int8 |✔ |✔ | +|Int16 |✔ |✔ | +|Int32 |✔ |✔ | +|Int64 |✔ |✔ | +|Int128 |✔ |✔ | +|Int256 |✔ |✔ | +|UInt8 |✔ |✔ | +|UInt16 |✔ |✔ | +|UInt32 |✔ |✔ | +|UInt64 |✔ |✔ | +|UInt128 |✔ |✔ | +|UInt256 |✔ |✔ | +|Float32 |✔ |✔ | +|Float64 |✔ |✔ | +|Decimal |✔ |✔ | +|Decimal32 |✔ |✔ | +|Decimal64 |✔ |✔ | +|Decimal128 |✔ |✔ | +|Decimal256 |✔ |✔ | +|Bool |✔ |✔ | +|String |✔ |✔ | +|FixedString |✔ |✔ | +|Nullable |✔ |✔ | +|Date |✔ |✔ | +|Date32 |✔ |✔ | +|DateTime |✔ |✔ | +|DateTime32 |✔ |✔ | +|DateTime64 |✔ |✔ | +|Interval |✗ |✗ | +|Enum |✔ |✔ | +|Enum8 |✔ |✔ | +|Enum16 |✔ |✔ | +|Array |✔ |✔ | +|Map |✔ |✔ | +|Nested |✔ |✔ | +|Tuple |✔ |✔ | +|UUID |✔ |✔ | +|IPv4 |✔ |✔ | +|IPv6 |✔ |✔ | +|Object |✗ |✔ | +|Point |✔ |✔ | +|Nothing |✔ |✔ | +|MultiPolygon |✔ |✔ | +|Ring |✔ |✔ | +|Polygon |✔ |✔ | +|SimpleAggregateFunction|✔ |✔ | +|AggregateFunction* |✔ |✔ | +|Variant |✔ |✗ | +|Dynamic |✔ |✗ | +|JSON |✔ |✗ | + +[ClickHouse Data Types](/reference/data-types/index) + + +**Partial support** + +- **AggregateFunction** — Only `groupBitmap` is supported for direct binary reads. For other aggregate functions (`min`, `max`, `avg`, etc.), use `-Merge` combinators in your query (e.g., `minMerge()`, `avgMerge()`) to resolve the state server-side. `SELECT * FROM table ...` is not supported for columns with `AggregateFunction` type. + + + +**Data type notes** + +- **Decimal** — `SET output_format_decimal_trailing_zeros=1` in 21.9+ for consistency. +- **Enum** — can be treated as both string and integer. +- **UInt64** — mapped to `long` in client-v1. + + +### Features {#features} + +Table of features of the clients: + +| Name | Client V2 | Client V1 | Comments +|----------------------------------------------|:---------:|:---------:|:---------:| +| Http Connection |✔ |✔ | | +| Http Compression (LZ4) |✔ |✔ | | +| Application Controlled Compression |✔ |✗ | | +| Server Response Compression - LZ4 |✔ |✔ | | +| Client Request Compression - LZ4 |✔ |✔ | | +| HTTPS |✔ |✔ | | +| Client SSL Cert (mTLS) |✔ |✔ | | +| Http Proxy |✔ |✔ | | +| POJO SerDe |✔ |✗ | | +| Connection Pool |✔ |✔ | When Apache HTTP Client used | +| Named Parameters |✔ |✔ | | +| Retry on failure |✔ |✔ | | +| Failover |✗ |✔ | | +| Load-balancing |✗ |✔ | | +| Server auto-discovery |✗ |✔ | | +| Log Comment |✔ |✔ | | +| Session Roles |✔ |✔ | | +| SSL Client Authentication |✔ |✔ | | +| SNI Configuration |✔ |✗ | | +| Session timezone |✔ |✔ | | + +JDBC Drive inherits same features as underlying client implementation. Other JDBC features are listed on its [page](/integrations/language-clients/java/jdbc). + +### Compatibility {#compatibility} + +- All projects in this repo are tested with all [active LTS versions](https://github.com/ClickHouse/ClickHouse/pulls?q=is%3Aopen+is%3Apr+label%3Arelease) of ClickHouse. +- [Support policy](https://github.com/ClickHouse/ClickHouse/blob/master/SECURITY.md#security-change-log-and-support) +- We recommend to upgrade client continuously to not miss security fixes and new improvements +- If you have an issue with migration to v2 API - [create an issue](https://github.com/ClickHouse/clickhouse-java/issues/new?assignees=&labels=v2-feedback&projects=&template=v2-feedback.md&title=) and we will respond! + +### Logging {#logging} + +Our Java language client uses [SLF4J](https://www.slf4j.org/) for logging. You can use any SLF4J-compatible logging framework, such as `Logback` or `Log4j`. +For example, if you're using Maven you could add the following dependency to your `pom.xml` file: + +```xml title="pom.xml" + + + + org.slf4j + slf4j-api + 2.0.16 + + + + + ch.qos.logback + logback-core + 1.5.16 + + + + + ch.qos.logback + logback-classic + 1.5.16 + + +``` + +#### Configuring logging {#configuring-logging} + +This is going to depend on the logging framework you're using. For example, if you're using `Logback`, you could configure logging in a file called `logback.xml`: + +```xml title="logback.xml" + + + + + [%d{yyyy-MM-dd HH:mm:ss}] [%level] [%thread] %logger{36} - %msg%n + + + + + + logs/app.log + true + + [%d{yyyy-MM-dd HH:mm:ss}] [%level] [%thread] %logger{36} - %msg%n + + + + + + + + + + + + +``` + +[Changelog](https://github.com/ClickHouse/clickhouse-java/blob/main/CHANGELOG.md) diff --git a/docs/clickhouse-docs/jdbc.mdx b/docs/clickhouse-docs/jdbc.mdx new file mode 100644 index 000000000..66f44e53e --- /dev/null +++ b/docs/clickhouse-docs/jdbc.mdx @@ -0,0 +1,1303 @@ +--- +sidebarTitle: 'JDBC' +keywords: ['clickhouse', 'java', 'jdbc', 'driver', 'integrate'] +description: 'ClickHouse JDBC driver' +slug: /integrations/language-clients/java/jdbc +title: 'JDBC driver' +doc_type: 'reference' +integration: + - support_level: 'core' + - category: 'language_client' +--- + +import WideTableWrapper from "/snippets/components/WideTableWrapper/WideTableWrapper.jsx"; + + + + + + +`clickhouse-jdbc` implements the standard JDBC interface using the latest java client. +We recommend using the latest java client directly if performance/direct access is critical. + + +## Environment requirements {#environment-requirements} + +- [OpenJDK](https://openjdk.java.net) version >= 8 + +### Setup {#setup} + + + + + ```xml + {/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */} + + com.clickhouse + clickhouse-jdbc + 0.9.8 + all + + ``` + + + + + ```kotlin + // https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc + implementation("com.clickhouse:clickhouse-jdbc:0.9.8:all") + ``` + + + + ```groovy + // https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc + implementation 'com.clickhouse:clickhouse-jdbc:0.9.8:all' + ``` + + + + +If you are using JDBC driver within an application that requires jar to be added to the classpath, you need to add download the jar from: +- [Maven Central](https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc) and add it to the classpath + - starting from version `0.9.4` there is an artifact https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc-all + - use qualifier `all` to get the jar with all shaded dependencies included. +- or from official repository [here](https://github.com/ClickHouse/clickhouse-java/releases) + +## Configuration {#configuration} + +**Driver Class**: `com.clickhouse.jdbc.ClickHouseDriver` + + +`com.clickhouse.jdbc.ClickHouseDriver` is a facade class for the new and old JDBC implementations. It uses the new JDBC implementation by default. +You can use the old JDBC implementation by setting the `clickhouse.jdbc.v1` **system** property to `true`. This property should be set before calling +Driver class. + +Alternative way to switch between version is to use Driver classes of each version directly: +- `com.clickhouse.jdbc.Driver` is new JDBC implementation (V2). +- `com.clickhouse.jdbc.DriverV1` is old JDBC implementation (V1). + + +**URL Syntax**: `jdbc:(ch|clickhouse)[:]://endpoint[:port][/][?param1=value1¶m2=value2][#tag1,tag2,...]`, for example: + +- `jdbc:clickhouse:http://localhost:8123` +- `jdbc:clickhouse:https://localhost:8443?ssl=true` + +There are a few things to note about the URL syntax: +- **only** one endpoint is allowed in the URL +- protocol should be specified when it isn't the default one - 'HTTP' +- port should be specified when it isn't the default one '8123' +- driver don't guess the protocol from the port, you need to specify it explicitly +- `ssl` parameter isn't required when protocol is specified. + +### Connection Properties +Main configuration parameters are defined in the [java client](/integrations/language-clients/java/client#client-configuration). They should be passed +as is to the driver. Driver has some own properties that aren't part of the client configuration they're listed below. + +**Driver properties**: +| Property | Default | Description | +|----------------------------------|---------|----------------------------------------------------------------| +| `disable_frameworks_detection` | `true` | Disable frameworks detection for User-Agent | +| `jdbc_ignore_unsupported_values` | `false` | Suppresses `SQLFeatureNotSupportedException` where is doesn't affect the driver work | +| `clickhouse.jdbc.v1` | `false` | Use older JDBC implementation instead of new JDBC | +| `default_query_settings` | `null` | Allows passing of default query settings with query operations | +| `jdbc_resultset_auto_close` | `true` | Automatically closes `ResultSet` when `Statement` is closed | +| `beta.row_binary_for_simple_insert` | `false` | Use `PreparedStatement` implementation based on `RowBinary` writer. Works only for `INSERT INTO ... VALUES` queries. | +| `jdbc_resultset_auto_close` | `true` | Automatically closes `ResultSet` when `Statement` is closed | +| `jdbc_use_max_result_rows` | `false` | Enables using server property `max_result_rows` to limit number of rows returned by query. When enabled, overrides user-set overflow mode. See JavaDoc for details. | +| `jdbc_sql_parser` | `JAVACC` | Configures which SQL parser to use. Choices: `ANTLR4`, `ANTLR4_PARAMS_PARSER`, `JAVACC`. | +| `remember_last_set_roles` | `true` | Remember last set roles for the connection. | + + +**Server Settings** + + +All server settings should be prefixed with `clickhouse_setting_` (same as for the client [configuration](/integrations/language-clients/java/client#server-settings)). + +```java +Properties config = new Properties(); +config.setProperty("user", "default"); +config.setProperty("password", getPassword()); + +// set server setting +config.put(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1"); + +Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", config); +``` + + +**Example configuration**: +```java +Properties properties = new Properties(); +properties.setProperty("user", "default"); +properties.setProperty("password", getPassword()); +properties.setProperty("client_name", "my-app-01"); // when http protocol is used it will be `http_user_agent` in the query log but not `client_name`. + +Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties); +``` + +what will be equivalent to the following JDBC URL: + +```sql +jdbc:ch:http://localhost:8123/?user=default&password=password&client_name=my-app-01 +// credentials shoud be passed in `Properties`. Here it is just for example. +``` +Note: no need to url encode JDBC URL or properties, they will be automatically encoded. + +**Readonly Profiles** + +We deliberately avoid adding default settings to connection properties to avoid problems with read-only profiles. +However some users need to pass format settings (for example to read JSON as String) and we recommend using `readonly=2` profile. +Read more about read-only profiles [here](/concepts/features/configuration/settings/constraints-on-settings#read-only). + +### Client Identification {#client-identification} + +There are two ways to identify application originated a request: set `com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME` via +connection properties or use `java.sql.Connection#setClientInfo(String name, String value)` method. + +```java showLineNumbers +Properties properties = new Properties(); +properties.setProperty(ClientConfigProperties.CLIENT_NAME.getKey(), "my-app-01"); +Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties); +``` + +```java showLineNumbers +conn.setClientInfo(com.clickhouse.jdbc.ClientInfoProperties.APPLICATION_NAME.getKey(), "my-app-01"); +``` + +Both ways will result in the following `http_user_agent` value in the query log: +``` +my-app-01/1.0 jdbc-v2/0.9.7 clickhouse-java-v2/0.9.6 (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4 +``` + +**Note:** We recommend using `app_name/version` format for `client_name` property because it helps to identify the application in the query log. + +### Operation Identification {#operation-identification} + +JDBC driver generates `query_id` for each operation (Currently it is included in server exceptions). + +To set `log_comment` for an operation use `com.clickhouse.jdbc.StatementImpl#getLocalSettings` method. This requires +`Statement` or `PreparedStatement` to be cast to `com.clickhouse.jdbc.StatementImpl` first. + +```java showLineNumbers +StatementImpl stmt = (StatementImpl) conn.createStatement(); +stmt.getLocalSettings().logComment("some-comment"); +``` + +**Note:** this approach work for single threaded uses of statement because `localSettings` is shared between threads. + +## Supported data types {#supported-data-types} + +JDBC driver supports the same data formats as the underlying [java client](/integrations/language-clients/java/index#supported-data-types). + +### JDBC Type Mapping {#jdbc-type-mapping} + +Following mapping applies to: +- `ResultSet#getObject(columnIndex)` - method will return object of the corresponding Java class. (`Int8` -> `java.lang.Byte`, `Int16` -> `java.lang.Short`, etc.) +- `ResultSetMetaData#getColumnType(columnIndex)` - method will return the corresponding JDBC type. (`Int8` -> `java.lang.Byte`, `Int16` -> `java.lang.Short`, etc.) + +There are few ways to change the mapping: +- `ResultSet#getObject(columnIndex, class)` - method will try to convert value to `class` type. There are some conversion limitations. See each section for details. + +**Numeric Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|-------------|-----------------------------| +| Int8 | TINYINT | java.lang.Byte | +| Int16 | SMALLINT | java.lang.Short | +| Int32 | INTEGER | java.lang.Integer | +| Int64 | BIGINT | java.lang.Long | +| Int128 | NUMERIC | java.math.BigInteger | +| Int256 | NUMERIC | java.math.BigInteger | +| UInt8 | SMALLINT | java.lang.Short | +| UInt16 | INTEGER | java.lang.Integer | +| UInt32 | BIGINT | java.lang.Long | +| UInt64 | NUMERIC | java.math.BigInteger | +| UInt128 | NUMERIC | java.math.BigInteger | +| UInt256 | NUMERIC | java.math.BigInteger | +| Float32 | FLOAT | java.lang.Float | +| Float64 | DOUBLE | java.lang.Double | +| Decimal32 | DECIMAL | java.math.BigDecimal | +| Decimal64 | DECIMAL | java.math.BigDecimal | +| Decimal128 | DECIMAL | java.math.BigDecimal | +| Decimal256 | DECIMAL | java.math.BigDecimal | +| Bool | BOOLEAN | java.lang.Boolean | + +- numeric types are interconvertible. So `Int8` can be get as `Float64` and vice versa.: + - `rs.getObject(1, Float64.class)` will return `Float64` value of `Int8` column. + - `rs.getLong(1)` will return `Long` value of `Int8` column. + - `rs.getByte(1)` can return `Byte` value of `Int16` column if it fits into `Byte`. +- conversion from wider to narrower type isn't recommend because of data coruption risk. +- `Bool` type acts as number, too. +- All number types can be read as `java.lang.String`. +- Storing java `Float.MAX_VALUE` as `Float` has issue (https://github.com/ClickHouse/clickhouse-java/issues/809). Saving same value as `Double` solves the issue. + +**String Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|-------------|-----------------------------| +| String | VARCHAR | java.lang.String | +| FixedString | VARCHAR | java.lang.String | + +- `String` can be read only as `java.lang.String` or `byte[]`. +- `FixedString` is read as is and will be padded with zeros to the length of the column. (For example `FixedString(10)` for `'John'` will be read as `'John\0\0\0\0\0\0\0\0\0'`.) + +**Enum Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|-------------|-----------------------------| +| Enum8 | VARCHAR | java.lang.String | +| Enum16 | VARCHAR | java.lang.String | + +- `Enum8` and `Enum16` are mapped to `java.lang.String` by default. +- Enum values can be read as numeric values using designtated getter method or `getObject(columnIndex, Integer.class)` method. +- `Enum16` is mapped to short and Enum8 is mapped to byte internally. Reading `Enum16` as byte should be avoided because of data coruption risk. +- Enum values can be set as string or numeric value in `PreparedStatement`. + +**Date/Time Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|-------------|-----------------------------| +| Date | DATE | java.sql.Date | +| Date32 | DATE | java.sql.Date | +| DateTime | TIMESTAMP | java.sql.Timestamp | +| DateTime64 | TIMESTAMP | java.sql.Timestamp | +| Time | TIME | java.sql.Time | +| Time64 | TIME | java.sql.Time | + +- Date / Time types are mapped to `java.sql` types for better compatibility with JDBC. However getting `java.time.LocalDate`, `java.time.LocalDateTime`, `java.time.LocalTime` is possible by using `ResultSet#getObject(columnIndex, Class)` with the corresponding class as the second argument. + - `rs.getObject(1, java.time.LocalDate.class)` will return `java.time.LocalDate` value of `Date` column. + - `rs.getObject(1, java.time.LocalDateTime.class)` will return `java.time.LocalDateTime` value of `DateTime` column. + - `rs.getObject(1, java.time.LocalTime.class)` will return `java.time.LocalTime` value of `Time` column. +- `Date`, `Date32`, `Time`, `Time64` isn't affected by the timezone of the server. +- `DateTime`, `DateTime64` is affected by the timezone of the server or session timezone. +- `DateTime` and `DateTime64` can be retrieved as `ZonedDateTime` by using `getObject(colIndex, ZonedDateTime.class)`. + +**Nested Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|-------------|-------------------------------------| +| Array | ARRAY | java.sql.Array | +| Tuple | OTHER | com.clickhouse.data.Tuple | +| Map | OTHER | java.util.Map | +| Nested | ARRAY | java.sql.Array | + +- `Array` is mapped to `java.sql.Array` by default to be compatible with JDBC. This is also done to give more information about returned array value. Useful for type inference. +- `Array` implements `getResultSet()` method to return `java.sql.ResultSet` with the same content as the original array. +- Collection types shouldn't be read as `java.lang.String` because it isn't a valid way to represent the data (Ex. there is no quoting for string values in array). +- `Map` is mapped to `OTHER` because value can be read only with `getObject(columnIndex, Class)` method. + - `Map` isn't a `java.sql.Struct` because it doesn't have named columns. +- `Tuple` is mapped to `Object[]` because it can contain different types and using `List` isn't valid. +- `Tuple` can be read as `Array` by using `getObject(columnIndex, Array.class)` method. In this case `Array#baseTypeName` will return `Tuple` column definition. + +**Array Element Type Metadata** + +`Array.getBaseTypeName()` returns the ClickHouse element type name; `Array.getBaseType()` returns the JDBC type code. +JDBC V2 preserves full type signatures (wrapper types, type parameters) that V1 strips. + +The general mapping rules for arrays are: + +| ClickHouse Type | `getBaseTypeName()` | `getBaseType()` | +|--------------------------------------------|------------------------------------------------|-----------------------------| +| `Array()` | `` | `` | +| `Array(())` | `()` | `` | +| `Array(Nullable())` | `Nullable()` | `` | +| `Array(LowCardinality())` | `LowCardinality()` | `` | +| `Array(Array(...()))` | `` (innermost element) | `` | +| `Array(Tuple(...))` | `Tuple(...)` (full definition) | `OTHER` | +| `Array(Enum8(...))` / `Array(Enum16(...))` | `Enum8(...)` / `Enum16(...)` (full definition) | `VARCHAR` | + +Notes on the rules above: +- Wrapper types (`Nullable`, `LowCardinality`) are preserved in `getBaseTypeName()` but `getBaseType()` resolves to the inner type's JDBC code. +- Nested arrays are flattened in the metadata: `getBaseTypeName()` returns the innermost non-array element type, not the immediate child. +- Parameterized types (`FixedString(N)`, full `Enum`/`Tuple` definitions) keep their parameters in `getBaseTypeName()`. + +**Examples:** +| ClickHouse Type (Example) | `getBaseTypeName()` | `getBaseType()` | +|----------------------------------------------------|---------------------------------------------|-----------------| +| Array(Int8) | Int8 | TINYINT | +| Array(Int16) | Int16 | SMALLINT | +| Array(Int32) | Int32 | INTEGER | +| Array(Int64) | Int64 | BIGINT | +| Array(UInt8) | UInt8 | SMALLINT | +| Array(UInt16) | UInt16 | INTEGER | +| Array(UInt32) | UInt32 | BIGINT | +| Array(UInt64) | UInt64 | NUMERIC | +| Array(Float32) | Float32 | FLOAT | +| Array(Float64) | Float64 | DOUBLE | +| Array(String) | String | VARCHAR | +| Array(FixedString(8)) | FixedString(8) | VARCHAR | +| Array(Bool) | Bool | BOOLEAN | +| Array(Date) | Date | DATE | +| Array(DateTime) | DateTime | TIMESTAMP | +| Array(UUID) | UUID | OTHER | +| Array(Nullable(Int32)) | Nullable(Int32) | INTEGER | +| Array(Nullable(String)) | Nullable(String) | VARCHAR | +| Array(LowCardinality(String)) | LowCardinality(String) | VARCHAR | +| Array(LowCardinality(Nullable(String))) | LowCardinality(Nullable(String)) | VARCHAR | +| Array(Array(Int32)) | Int32 | INTEGER | +| Array(Array(Array(String))) | String | VARCHAR | +| Array(Tuple(name String, val Int32)) | Tuple(name String, val Int32) | OTHER | +| Array(Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3)) | Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3) | VARCHAR | + +- In V2, `getBaseTypeName()` preserves the full type signature including wrapper types (`Nullable`, `LowCardinality`) and type parameters (`FixedString(8)`, full `Enum` and `Tuple` definitions). V1 strips these and returns only the base type name. +- `Tuple` arrays use `OTHER (1111)` in V2 instead of `STRUCT (2002)` because ClickHouse tuples have named fields, which `java.sql.Struct` does not support. +- `UUID` arrays use `OTHER (1111)` in V2, matching the scalar `UUID` mapping. +- `Enum` values map to `VARCHAR` — enum members are identified by string name regardless of their underlying numeric encoding. + +**Writing Arrays** + +Use `java.sql.Connection#createArrayOf` to instantiate `java.sql.Array` object. This object is designed to make array handling unified across different databases. +Connection is required to pass configuration to Array factory method. + +The method accepts two arguments: +- `typeName` - type name of the array elements. For example `Array(Int32)` -> `"Int32"`. +- `elements` - actual array elements. For example `[[1, 2, 3], [4, 5, 6]]` -> `new Integer[][] {{1, 2, 3}, {4, 5, 6}}`. + +Tuple can be presented as `Object[]` or as `java.sql.Struct` (See how to write tuples bellow). + +**Example** +```java +try (Connection conn = ...) { + Array array = conn.createArrayOf("Int32", new Integer[][] {{1, 2, 3}, {4, 5, 6}}); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (arr) VALUES (?)")) { + ps.setArray(1, array); + ps.executeUpdate(); + } +} +``` + +**Reading Arrays** + +Use `ResultSet#getArray(columnIndex)` to read `Array` object. Object can be used to access array of any nested depth. +`Array#getResultSet()` method can be used to read array elements in more unified way as `java.sql.ResultSet`. It is useful +when exact type of array elements is unknown. + +**Example** +```java +try (Connection conn = ...) { + try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Array(Int32)")) { + ps.setArray(1, array); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Array array = rs.getArray(1); + + Object[] arr = (Object[]) array; + Arrays.stream(arr).forEach(this::handleArrayElement); + + // or by using `ResultSet` + ResultSet resultSet = array.getResultSet(); + while (resultSet.next()) { + // ... + } + } + } + } +} +``` + +**Writing Tuples** + +Tuples are mapped to `com.clickhouse.data.Tuple` object and should be written as this object by calling `setObject(columnIndex, tuple)` method. +It is possible to use `java.sql.Struct` object to write tuples for better portability. + +**Example** +```java +try (Connection conn = ...) { + Tuple tuple = new Tuple(1, "test", LocalDate.parse("2026-03-02")); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) { + ps.setObject(1, tuple); + ps.executeUpdate(); + } +} + +try (Connection conn = ...) { + Struct struct = conn.createStruct("Tuple(Int32, String, Date)", new Object[] {1, "test", LocalDate.parse("2026-03-02")}); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) { + ps.setStruct(1, struct); + ps.executeUpdate(); + } +} +``` + +**Reading Tuples** + +The method `getObject(columnIndex)` will return `Object[]`. Tuples can be read as `java.sql.Array` by using `getObject(columnIndex, Array.class)` method. + +**Example** +```java +try (Connection conn = ...) { + try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::Tuple(String, Int32, Date)")) { + Array tuple = conn.createArrayOf("Tuple(String, Int32, Date)", new Object[]{"test", 123, LocalDate.parse("2026-03-02")}); + stmt.setObject(1, tuple); + try (ResultSet rs = stmt.executeQuery()) { + rs.next(); + Array dbTuple = rs.getArray(1); + Assert.assertEquals(dbTuple, tuple); + Object arr = rs.getObject(1); + Assert.assertEquals(arr, tuple.getArray()); + } + } +} + +``` + +**Writing Maps** + +Map can be written only as `java.collections.Map` object because this types requires key-value pairs (`java.sql.Struct` doesn't support key-value pairs). + +**Example** +```java +try (Connection conn = ...) { + Map map = new HashMap<>(); + map.put("key1", 1); + map.put("key2", 2); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (map) VALUES (?)")) { + ps.setObject(1, map); + ps.executeUpdate(); + } +} +``` + +**Reading Maps** + +Map can be read as `java.collections.Map` object by using `getObject(columnIndex, Map.class)` method. + +**Example** +```java +try (Connection conn = ...) { + try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Map(String, Int32)")) { + ps.setStruct(1, struct); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Map map = rs.getObject(1, Map.class); + // ... + } + } + } +} +``` + +**Writing Nested** + +Use `java.sql.Connection#createStruct` to instantiate `java.sql.Struct` object. This object is designed to make nested handling unified across different databases. +Connection is required to pass configuration to Struct factory method. + +The method accepts two arguments: +- `typeName` - type name of the nested elements. For example `Nested(Tuple(Int32, String))` -> `"Nested(Tuple(Int32, String))"`. +- `elements` - actual nested elements. For example `[1, 'test']` -> `new Object[] {1, 'test'}`. + +**Example** +```java +try (Connection conn = ...) { + Struct struct = conn.createStruct("Nested(Tuple(Int32, String))", new Object[] {1, 'test'}); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (nested) VALUES (?)")) { + ps.setStruct(1, struct); + ps.executeUpdate(); + } +} +``` + +**Reading Nested** + +Use `ResultSet#getStruct(columnIndex, StructDescriptor)` to read `Nested` object. Object can be used to access nested of any nested depth. +`Struct#getResultSet()` method can be used to read nested elements in more unified way as `java.sql.ResultSet`. It is useful +when exact type of nested elements is unknown. + +**Example** +```java +try (Connection conn = ...) { + try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Nested(Tuple(Int32, String))")) { + ps.setStruct(1, struct); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Struct struct = rs.getStruct(1); + Object[] tuple = (Object[]) struct; + Arrays.stream(tuple).forEach(this::handleTupleElement); + + // or by using `ResultSet` + ResultSet resultSet = struct.getResultSet(); + while (resultSet.next()) { + // ... + } + } + } + } +} +``` + +**Geo Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|------------------------------|-----------------------------| +| Point | OTHER | double[] | +| Ring | OTHER | double[][] | +| Polygon | OTHER | double[][][] | +| MultiPolygon | OTHER | double[][][][] | + +**Nullable and LowCardinality Types** + +- `Nullable` and `LowCardinality` are special types that wrap other types. +- `Nullable` affects how type names are returned in `ResultSetMetaData` + +**Special Types** + +| ClickHouse Type | JDBC Type | Java Class | +|-----------------------------|------------------------------|-----------------------------| +| UUID | OTHER | java.util.UUID | +| IPv4 | OTHER | java.net.Inet4Address | +| IPv6 | OTHER | java.net.Inet6Address | +| JSON | OTHER | java.lang.String | +| AggregateFunction | OTHER | (binary representation) | +| SimpleAggregateFunction | (wrapped type) | (wrapped class) | + +- `UUID` isn't JDBC standard type. However it is part of JDK. By default `java.util.UUID` is returned on `getObject()` method. +- `UUID` can be read/written as `String` by using `getObject(columnIndex, String.class)` method. +- `IPv4` and `IPv6` aren't JDBC standard types. However they're part of JDK. By default `java.net.Inet4Address` and `java.net.Inet6Address` are returned on `getObject()` method. +- `IPv4` and `IPv6` can be read/written as `String` by using `getObject(columnIndex, String.class)` method. + +**JSON Type** + +JSON type is mapped to `Map` by default where keys are JSON object keys and values are JSON object values. +For example: +```json +{ + "key1": "value1", + "key2": ["value2", "value3"] + "key3": { + "key4": "value4", + "key5": "value5" + } +} +``` +will be mapped to: + +```java +Map map = new HashMap<>(); +map.put("key1", "value1"); +map.put("key2", Arrays.asList("value2", "value3")); +map.put("key3", new HashMap() {{ + put("key4", "value4"); + put("key5", "value5"); +}}); +``` +There is more convinient option to read JSON as String by passing server setting `jdbc_read_json_as_string=true` to connection properties. +This makes driver return JSON values as String and can be used to parse using any JSON library. + +```java +Properties properties = new Properties(); +properties.setProperty( + ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), + "1"); +try (Connection conn = DriverManager.getConnection(url, properties)) { + try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_json ORDER BY order")) { + while (rs.next()) { + String json = rs.getString("json"); + // ... + } + } +} +``` + +Starting ClickHouse version 25.8 numbers are no longer quoted by default. For older versions you can disable quoting by passing server settings to connection properties: +```java +Properties properties = new Properties(); +properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0"); +properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0"); +properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0"); +``` + +### Handling Dates, Times, and Timezones {#handling-dates-times-and-timezones} + +Please read [Date/Time Guide](/integrations/language-clients/java/date-time-guide) that explains common pitfalls +and logic of the driver when handling Date/Time and Timestamps. + +## Creating Connection {#creating-connection} + +```java +String url = "jdbc:ch://my-server:8123/system"; + +Properties properties = new Properties(); +DataSource dataSource = new DataSource(url, properties);//DataSource or DriverManager are the main entry points +try (Connection conn = dataSource.getConnection()) { +... // do something with the connection +``` + +## Supplying Credentials and Settings {#supplying-credentials-and-settings} + +```java showLineNumbers +String url = "jdbc:ch://localhost:8123?jdbc_ignore_unsupported_values=true&socket_timeout=10"; + +Properties info = new Properties(); +info.put("user", "default"); +info.put("password", "password"); +info.put("database", "some_db"); + +//Creating a connection with DataSource +DataSource dataSource = new DataSource(url, info); +try (Connection conn = dataSource.getConnection()) { +... // do something with the connection +} + +//Alternate approach using the DriverManager +try (Connection conn = DriverManager.getConnection(url, info)) { +... // do something with the connection +} +``` + +## Simple Statement {#simple-statement} + +```java showLineNumbers + +try (Connection conn = dataSource.getConnection(...); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery("select * from numbers(50000)"); + while(rs.next()) { + // ... + } +} +``` + +## Insert {#insert} + +```java showLineNumbers +try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable VALUES (?, ?)")) { + ps.setString(1, "test"); // id + ps.setObject(2, LocalDateTime.now()); // timestamp + ps.addBatch(); + ... + ps.executeBatch(); // stream everything on-hand into ClickHouse +} +``` + +## `HikariCP` {#hikaricp} + +```java showLineNumbers +// connection pooling won't help much in terms of performance, +// because the underlying implementation has its own pool. +// for example: HttpURLConnection has a pool for sockets +HikariConfig poolConfig = new HikariConfig(); +poolConfig.setConnectionTimeout(5000L); +poolConfig.setMaximumPoolSize(20); +poolConfig.setMaxLifetime(300_000L); +poolConfig.setDataSource(new ClickHouseDataSource(url, properties)); + +try (HikariDataSource ds = new HikariDataSource(poolConfig); + Connection conn = ds.getConnection(); + Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("SELECT * FROM system.numbers LIMIT 3")) { + while (rs.next()) { + // handle row + log.info("Integer: {}, String: {}", rs.getInt(1), rs.getString(1));//Same column but different types + } +} +``` + +## More Information {#more-information} +For more information, see our [GitHub repository](https://github.com/ClickHouse/clickhouse-java) and [Java Client documentation](/integrations/language-clients/java/client). + +## Troubleshooting {#troubleshooting} +### Logging {#logging} +The driver uses [slf4j](https://www.slf4j.org/) for logging, and will use the first available implementation on the `classpath`. + +### Resolving JDBC Timeout on Large Inserts {#resolving-jdbc-timeout-on-large-inserts} + +When performing large inserts in ClickHouse with long execution times, you may encounter JDBC timeout errors like: + +```plaintext +Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443] +``` +These errors can disrupt the data insertion process and affect system stability. To address this issue you may need to adjust a few timeout settings in the client's OS. + +#### Mac OS {#mac-os} + +On Mac OS, the following settings can be adjusted to resolve the issue: + +- `net.inet.tcp.keepidle`: 60000 +- `net.inet.tcp.keepintvl`: 45000 +- `net.inet.tcp.keepinit`: 45000 +- `net.inet.tcp.keepcnt`: 8 +- `net.inet.tcp.always_keepalive`: 1 + +#### Linux {#linux} + +On Linux, the equivalent settings alone may not resolve the issue. Additional steps are required due to the differences in how Linux handles socket keep-alive settings. Follow these steps: + +1. Adjust the following Linux kernel parameters in `/etc/sysctl.conf` or a related configuration file: + + - `net.inet.tcp.keepidle`: 60000 + - `net.inet.tcp.keepintvl`: 45000 + - `net.inet.tcp.keepinit`: 45000 + - `net.inet.tcp.keepcnt`: 8 + - `net.inet.tcp.always_keepalive`: 1 + - `net.ipv4.tcp_keepalive_intvl`: 75 + - `net.ipv4.tcp_keepalive_probes`: 9 + - `net.ipv4.tcp_keepalive_time`: 60 (You may consider lowering this value from the default 300 seconds) + +2. After modifying the kernel parameters, apply the changes by running the following command: + +```shell +sudo sysctl -p +``` + +After Setting those settings, you need to ensure that your client enables the Keep Alive option on the socket: + +```java +properties.setProperty("socket_keepalive", "true"); +``` + +## Migration Guide {#migration-guide} + +### Key Changes {#key-changes} + +| Feature | V1 (Old) | V2 (New) | +|---------|----------|----------| +| Transaction Support | Partially supported | Not supported | +| Response Column Renaming | Partially supported | Not supported | +| Multi-Statement SQL | Not supported | Not allowed | +| Named Parameters | Supported | Not supported (not in JDBC spec) | +| Streaming Data With `PreparedStatement` | Supported | Not supported | + +- JDBC V2 is implemented to be more lightweight and some features were removed. + - Streaming Data isn't supported in JDBC V2 because it isn't part of the JDBC spec and Java. +- JDBC V2 expects explicit configuration. No failover defaults. + - Protocol should be specified in the URL. No implicit protocol detection using port numbers. + +### Configuration Changes {#configuration-changes} + +There are only two enums: +- `com.clickhouse.jdbc.DriverProperties` - the driver own configuration properties. +- `com.clickhouse.client.api.ClientConfigProperties` - the client configuration properties. Client configuration + changes are described in the [Java Client documentation](/integrations/language-clients/java/client#migration_from_v1_config). + +Connection properties are parsed in the following way: +- URL is parsed first for properties. They override all other properties. +- Driver properties aren't passed to the client. +- Endpoints (host, port, protocol) are parsed from the URL. + +Example: +```java +String url = "jdbc:ch://my-server:8443/default?" + + "jdbc_ignore_unsupported_values=true&" + + "socket_rcvbuf=800000"; + +Properties properties = new Properties(); +properties.setProperty("socket_rcvbuf", "900000"); +try (Connection conn = DriverManager.getConnection(url, properties)) { + // Connection will use socket_rcvbuf=800000 and jdbc_ignore_unsupported_values=true + // Endpoints: my-server:8443 protocol: http (not secure) + // Database: default +} +``` + +### Data Types Changes {#data-types-changes} + +**Numeric Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| Int8 | ✅ | TINYINT | java.lang.Byte | TINYINT | java.lang.Byte | +| Int16 | ✅ | SMALLINT | java.lang.Short | SMALLINT | java.lang.Short | +| Int32 | ✅ | INTEGER | java.lang.Integer | INTEGER | java.lang.Integer | +| Int64 | ✅ | BIGINT | java.lang.Long | BIGINT | java.lang.Long | +| Int128 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger | +| Int256 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger | +| UInt8 | ❌ | SMALLINT | java.lang.Short | SMALLINT | com.clickhouse.data.value.UnsignedByte | +| UInt16 | ❌ | INTEGER | java.lang.Integer | INTEGER | com.clickhouse.data.value.UnsignedShort | +| UInt32 | ❌ | BIGINT | java.lang.Long | BIGINT | com.clickhouse.data.value.UnsignedInteger | +| UInt64 | ❌ | NUMERIC | java.math.BigInteger | NUMERIC | com.clickhouse.data.value.UnsignedLong | +| UInt128 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger | +| UInt256 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger | +| Float32 | ✅ | FLOAT | java.lang.Float | FLOAT | java.lang.Float | +| Float64 | ✅ | DOUBLE | java.lang.Double | DOUBLE | java.lang.Double | +| Decimal32 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal | +| Decimal64 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal | +| Decimal128 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal | +| Decimal256 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal | +| Bool | ✅ | BOOLEAN | java.lang.Boolean | BOOLEAN | java.lang.Boolean | + +- The biggest differences is that unsigned types are mapped to java types for better portability. + +**String Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| String | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String | +| FixedString | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String | + +- `FixedString` is read as is in both versions. For example `FixedString(10)` for `'John'` will be read as `'John\0\0\0\0\0\0\0\0\0'`. +- When `PreparedStatement#setBytes` is used it will be converted to `unhex('')` and then read as `String`. +- Strings are stored in UTF-8 encoding. + +**Date/Time Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| Date | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate | +| Date32 | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate | +| DateTime | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP_WITH_TIMEZONE | java.time.OffsetDateTime | +| DateTime64 | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP_WITH_TIMEZONE | java.time.OffsetDateTime | +| Time | ✅ | TIME | java.sql.Time | new type/not supported | new type/not supported | +| Time64 | ✅ | TIME | java.sql.Time | new type/not supported | new type/not supported | + +- `Time` and `Time64` are supported in V2 only as new types. +- `DateTime` and `DateTime64` are mapped to `java.sql.Timestamp` for better compatibility with JDBC. + +**Enum Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| Enum | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String | +| Enum8 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String | +| Enum16 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String | + +**Nested Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| Array | ❌ | ARRAY | java.sql.Array | ARRAY | Object[] or array of primitive types | +| Tuple | ❌ | OTHER | Object[] | STRUCT | java.sql.Struct | +| Map | ❌ | JAVA_OBJECT | java.util.Map | STRUCT | java.util.Map | +| Nested | ❌ | ARRAY | java.sql.Array | STRUCT | java.sql.Struct | + +- In V2 `Array` is mapped to `java.sql.Array` by default to be compatible with JDBC. This is also done to give more information about returned array value. Useful for type inference. +- In V2 `Array` implements `getResultSet()` method to return `java.sql.ResultSet` with the same content as the original array. +- V1 uses `STRUCT` for `Map` but returns `java.util.Map` object always. V2 fixes this by mapping `Map` to `JAVA_OBJECT`. +- V1 uses `STRUCT` for `Tuple` but returns `List` object always. V2 maps `Tuple` to `OTHER` and returns `Object[]` by default. +- V2 introduces `com.clickhouse.data.Tuple#Tuple` to write tuples. It simplifies determining if value is a tuple or and array. +- `PreparedStatement#setBytes` and `ResultSet#getBytes` cannot be used with collection types. These methods are designed to work with binary strings. +- Normally `java.sql.Array` is used to write and read Array types. JDBC driver has full support for this. +- V2 `Nested` is mapped to `Array` and presents it as array of tuples. +- V2 has partial support for `java.sql.Struct` because it very similar to Array type and doesn't support key-value pairs. `Struct` can be used to write `Tuple` values. + +**Geo Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| Point | ✅ | OTHER | double[] | OTHER | double[] | +| Ring | ✅ | OTHER | double[][] | OTHER | double[][] | +| Polygon | ✅ | OTHER | double[][][] | OTHER | double[][][] | +| MultiPolygon | ✅ | OTHER | double[][][][] | OTHER | double[][][][] | + +**Nullable and LowCardinality Types** + +- `Nullable` and `LowCardinality` are special types that wrap other types. +- No changes are made to these types in V2. + +**Special Types** + +| ClickHouse Type | Compatible with V1 | JDBC Type (V2) | Java Class (V2) | JDBC Type (V1) | Java Class (V1) | +|-----------------------------|-------------------|----------------------------|----------------------------|----------------------------|-----------------------------------------| +| JSON | ❌ | OTHER | java.lang.String | not supported | not supported | +| AggregateFunction | ✅ | OTHER | (binary representation) | OTHER | (binary representation) | +| SimpleAggregateFunction | ✅ | (wrapped type) | (wrapped class) | (wrapped type) | (wrapped class) | +| UUID | ✅ | OTHER | java.util.UUID | VARCHAR | java.util.UUID | +| IPv4 | ✅ | OTHER | java.net.Inet4Address | VARCHAR | java.net.Inet4Address | +| IPv6 | ✅ | OTHER | java.net.Inet6Address | VARCHAR | java.net.Inet6Address | +| Dynamic | ❌ | OTHER | java.Object | not supported | not supported | +| Variant | ❌ | OTHER | java.Object | not supported | not supported | + +- V1 uses `VARCHAR` for `UUID` but returns `java.util.UUID` object always. V2 fixes this by mapping `UUID` to `OTHER` and returns `java.util.UUID` object. +- V1 uses `VARCHAR` for `IPv4` and `IPv6` but returns `java.net.Inet4Address` and `java.net.Inet6Address` objects always. V2 fixes this by mapping `IPv4` and `IPv6` to `OTHER` and returns `java.net.Inet4Address` and `java.net.Inet6Address` objects. +- `Dynamic` and `Variant` are new types in V2. Not supported in V1. +- `JSON` is based on `Dynamic` type. Therefore it is supported only in V2. +- IPv4 and IPv6 values can be read as `byte[]` by using `getBytes(columnIndex)` method. However it is recommended to use designated classes for these types. +- V2 do not support reading IP address as numeric values because convertion is better implementation in InetAddress classes. + +### Database Metadata Changes {#database-metadata-changes} + +- V2 uses only `Schema` term to name databases. `Catalog` term is reserved for future use. +- V2 returns `false` for `DatabaseMetaData.supportsTransactions()` and `DatabaseMetaData.supportsSavepoints()`. This will be changed in the future development. +- In `DatabaseMetaData.getTypeInfo()`, the `LITERAL_PREFIX` and `LITERAL_SUFFIX` columns now return `null` for data types where prefixes and suffixes are not expected (e.g., numeric types). +In V1, these columns returned non-null values for such types. These columns should be used when generating SQL queries to properly quote literal values according to their data type. + + + + + +`clickhouse-jdbc` implements the standard JDBC interface. Being built on top of [clickhouse-client](/integrations/connectors/sql-clients/sql-console), it provides additional features like custom type mapping, transaction support, and standard synchronous `UPDATE` and `DELETE` statements, etc., so that it can be easily used with legacy applications and tools. + + +Latest JDBC (0.7.2) version uses Client-V1 + + +`clickhouse-jdbc` API is synchronous, and generally, it has more overheads(e.g., SQL parsing and type mapping/conversion, etc.). Consider [clickhouse-client](/integrations/connectors/sql-clients/sql-console) when performance is critical or if you prefer a more direct way to access ClickHouse. + +## Environment requirements {#v07-environment-requirements} + +- [OpenJDK](https://openjdk.java.net) version >= 8 + +### Setup {#v07-setup} + + + + + ```xml + {/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */} + + com.clickhouse + clickhouse-jdbc + 0.7.2 + {/* use uber jar with all dependencies included, change classifier to http for smaller jar */} + shaded-all + + ``` + + + + + ```kotlin + // https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc + // use uber jar with all dependencies included, change classifier to http for smaller jar + implementation("com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all") + ``` + + + + ```groovy + // https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc + // use uber jar with all dependencies included, change classifier to http for smaller jar + implementation 'com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all' + ``` + + + + +Since version `0.5.0`, we're using Apache HTTP Client that's packed the Client. Since there isn't a shared version of the package, you need to add a logger as a dependency. + + + + + ```xml + {/* https://mvnrepository.com/artifact/org.slf4j/slf4j-api */} + + org.slf4j + slf4j-api + 2.0.16 + + ``` + + + + + ```kotlin + // https://mvnrepository.com/artifact/org.slf4j/slf4j-api + implementation("org.slf4j:slf4j-api:2.0.16") + ``` + + + + ```groovy + // https://mvnrepository.com/artifact/org.slf4j/slf4j-api + implementation 'org.slf4j:slf4j-api:2.0.16' + ``` + + + + +## Configuration {#v07-configuration} + +**Driver Class**: `com.clickhouse.jdbc.ClickHouseDriver` + +**URL Syntax**: `jdbc:(ch|clickhouse)[:]://endpoint1[,endpoint2,...][/][?param1=value1¶m2=value2][#tag1,tag2,...]`, for example: + +- `jdbc:ch://localhost` is same as `jdbc:clickhouse:http://localhost:8123` +- `jdbc:ch:https://localhost` is same as `jdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICT` +- `jdbc:ch:grpc://localhost` is same as `jdbc:clickhouse:grpc://localhost:9100` + +**Connection Properties**: + +| Property | Default | Description | +| ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `continueBatchOnError` | `false` | Whether to continue batch processing when error occurred | +| `createDatabaseIfNotExist` | `false` | Whether to create database if it doesn't exist | +| `custom_http_headers` | | comma separated custom http headers, for example: `User-Agent=client1,X-Gateway-Id=123` | +| `custom_http_params` | | comma separated custom http query parameters, for example: `extremes=0,max_result_rows=100` | +| `nullAsDefault` | `0` | `0` - treat null value as is and throw exception when inserting null into non-nullable column; `1` - treat null value as is and disable null-check for inserting; `2` - replace null to default value of corresponding data type for both query and insert | +| `jdbcCompliance` | `true` | Whether to support standard synchronous UPDATE/DELETE and fake transaction | +| `typeMappings` | | Customize mapping between ClickHouse data type and Java class, which will affect result of both [`getColumnType()`](https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html#getColumnType-int-) and [`getObject(Class<>?>`)](https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html#getObject-java.lang.String-java.lang.Class-). For example: `UInt128=java.lang.String,UInt256=java.lang.String` | +| `wrapperObject` | `false` | Whether [`getObject()`](https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html#getObject-int-) should return java.sql.Array / java.sql.Struct for Array / Tuple. | + +Note: please refer to [JDBC specific configuration](https://github.com/ClickHouse/clickhouse-java/blob/main/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcConfig.java) for more. + +## Supported data types {#v07-supported-data-types} + +JDBC driver supports same data formats as client library does. + + +- AggregatedFunction - :warning: doesn't support `SELECT * FROM table ...` +- Decimal - `SET output_format_decimal_trailing_zeros=1` in 21.9+ for consistency +- Enum - can be treated as both string and integer +- UInt64 - mapped to `long` (in client-v1) + + +## Creating Connection {#v07-creating-connection} + +```java +String url = "jdbc:ch://my-server/system"; // use http protocol and port 8123 by default + +Properties properties = new Properties(); + +ClickHouseDataSource dataSource = new ClickHouseDataSource(url, properties); +try (Connection conn = dataSource.getConnection("default", "password"); + Statement stmt = conn.createStatement()) { +} +``` + +## Simple Statement {#v07-simple-statement} + +```java showLineNumbers + +try (Connection conn = dataSource.getConnection(...); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery("select * from numbers(50000)"); + while(rs.next()) { + // ... + } +} +``` + +## Insert {#v07-insert} + + +- Use `PreparedStatement` instead of `Statement` + + +It's easier to use but slower performance compare to input function (see below): + +```java showLineNumbers +try (PreparedStatement ps = conn.prepareStatement("insert into mytable(* except (description))")) { + ps.setString(1, "test"); // id + ps.setObject(2, LocalDateTime.now()); // timestamp + ps.addBatch(); // parameters will be write into buffered stream immediately in binary format + ... + ps.executeBatch(); // stream everything on-hand into ClickHouse +} +``` + +### With input table function {#with-input-table-function} + +An option with great performance characteristics: + +```java showLineNumbers +try (PreparedStatement ps = conn.prepareStatement( + "insert into mytable select col1, col2 from input('col1 String, col2 DateTime64(3), col3 Int32')")) { + // The column definition will be parsed so the driver knows there are 3 parameters: col1, col2 and col3 + ps.setString(1, "test"); // col1 + ps.setObject(2, LocalDateTime.now()); // col2, setTimestamp is slow and not recommended + ps.setInt(3, 123); // col3 + ps.addBatch(); // parameters will be write into buffered stream immediately in binary format + ... + ps.executeBatch(); // stream everything on-hand into ClickHouse +} +``` +- [input function doc](/reference/functions/table-functions/input) whenever possible + +### Insert with placeholders {#insert-with-placeholders} + +This option is recommended only for small inserts because it would require a long SQL expression (that will be parsed on client side and it will consume CPU & Memory): + +```java showLineNumbers +try (PreparedStatement ps = conn.prepareStatement("insert into mytable values(trim(?),?,?)")) { + ps.setString(1, "test"); // id + ps.setObject(2, LocalDateTime.now()); // timestamp + ps.setString(3, null); // description + ps.addBatch(); // append parameters to the query + ... + ps.executeBatch(); // issue the composed query: insert into mytable values(...)(...)...(...) +} +``` + +## Handling DateTime and time zones {#handling-datetime-and-time-zones} + +Please to use `java.time.LocalDateTime` or `java.time.OffsetDateTime` instead of `java.sql.Timestamp`, and `java.time.LocalDate` instead of `java.sql.Date`. + +```java showLineNumbers +try (PreparedStatement ps = conn.prepareStatement("select date_time from mytable where date_time > ?")) { + ps.setObject(2, LocalDateTime.now()); + ResultSet rs = ps.executeQuery(); + while(rs.next()) { + LocalDateTime dateTime = (LocalDateTime) rs.getObject(1); + } + ... +} +``` + +## Handling `AggregateFunction` {#handling-aggregatefunction} + + +Direct binary reading of `AggregateFunction` state is only supported for `groupBitmap`. For other aggregate functions (`min`, `max`, `avg`, etc.), use `-Merge` combinators in your query (e.g., `SELECT minMerge(min_state) FROM ...`) to resolve the aggregate state server-side and return a plain value. + + +```java showLineNumbers +// batch insert using input function +try (ClickHouseConnection conn = newConnection(props); + Statement s = conn.createStatement(); + PreparedStatement stmt = conn.prepareStatement( + "insert into test_batch_input select id, name, value from input('id Int32, name Nullable(String), desc Nullable(String), value AggregateFunction(groupBitmap, UInt32)')")) { + s.execute("drop table if exists test_batch_input;" + + "create table test_batch_input(id Int32, name Nullable(String), value AggregateFunction(groupBitmap, UInt32))engine=Memory"); + Object[][] objs = new Object[][] { + new Object[] { 1, "a", "aaaaa", ClickHouseBitmap.wrap(1, 2, 3, 4, 5) }, + new Object[] { 2, "b", null, ClickHouseBitmap.wrap(6, 7, 8, 9, 10) }, + new Object[] { 3, null, "33333", ClickHouseBitmap.wrap(11, 12, 13) } + }; + for (Object[] v : objs) { + stmt.setInt(1, (int) v[0]); + stmt.setString(2, (String) v[1]); + stmt.setString(3, (String) v[2]); + stmt.setObject(4, v[3]); + stmt.addBatch(); + } + int[] results = stmt.executeBatch(); + ... +} + +// use bitmap as query parameter +try (PreparedStatement stmt = conn.prepareStatement( + "SELECT bitmapContains(my_bitmap, toUInt32(1)) as v1, bitmapContains(my_bitmap, toUInt32(2)) as v2 from {tt 'ext_table'}")) { + stmt.setObject(1, ClickHouseExternalTable.builder().name("ext_table") + .columns("my_bitmap AggregateFunction(groupBitmap,UInt32)").format(ClickHouseFormat.RowBinary) + .content(new ByteArrayInputStream(ClickHouseBitmap.wrap(1, 3, 5).toBytes())) + .asTempTable() + .build()); + ResultSet rs = stmt.executeQuery(); + Assert.assertTrue(rs.next()); + Assert.assertEquals(rs.getInt(1), 1); + Assert.assertEquals(rs.getInt(2), 0); + Assert.assertFalse(rs.next()); +} +``` + +
    + +## Configuring HTTP library {#v07-configuring-http-library} + +The ClickHouse JDBC connector supports three HTTP libraries: [`HttpClient`](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html), [`HttpURLConnection`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html), and [Apache `HttpClient`](https://hc.apache.org/httpcomponents-client-5.2.x/). + + +`HttpClient` is only supported in JDK 11 or above. + + +The JDBC driver uses `HttpClient` by default. You can change the HTTP library used by the ClickHouse JDBC connector by setting the following property: + +```java +properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT"); +``` + +Here is a full list of the corresponding values: + +| Property Value | HTTP Library | +|---------------------|---------------------| +| HTTP_CLIENT | `HttpClient` | +| HTTP_URL_CONNECTION | `HttpURLConnection` | +| APACHE_HTTP_CLIENT | Apache `HttpClient` | + +
    + +## Connect to ClickHouse with SSL {#connect-to-clickhouse-with-ssl} + +To establish a secure JDBC connection to ClickHouse using SSL, you need to configure your JDBC properties to include SSL parameters. This typically involves specifying SSL properties such as `sslmode` and `sslrootcert` in your JDBC URL or Properties object. + +## SSL Properties {#ssl-properties} + +| Name | Default Value | Optional Values | Description | +| ------------------ | ------------- | --------------- |----------------------------------------------------------------------------------| +| `ssl` | false | true, false | Whether to enable SSL/TLS for the connection | +| `sslmode` | strict | strict, none | Whether to verify SSL/TLS certificate | +| `sslrootcert` | | | Path to SSL/TLS root certificates | +| `sslcert` | | | Path to SSL/TLS certificate | +| `sslkey` | | | RSA key in PKCS#8 format | +| `key_store_type` | | JKS, PKCS12 | Specifies the type or format of the `KeyStore`/`TrustStore` file | +| `trust_store` | | | Path to the `TrustStore` file | +| `key_store_password` | | | Password needed to access the `KeyStore` file specified in the `KeyStore` config | + +These properties ensure that your Java application communicates with the ClickHouse server over an encrypted connection, enhancing data security during transmission. + +```java showLineNumbers + String url = "jdbc:ch://your-server:8443/system"; + + Properties properties = new Properties(); + properties.setProperty("ssl", "true"); + properties.setProperty("sslmode", "strict"); // NONE to trust all servers; STRICT for trusted only + properties.setProperty("sslrootcert", "/mine.crt"); + try (Connection con = DriverManager + .getConnection(url, properties)) { + + try (PreparedStatement stmt = con.prepareStatement( + + // place your code here + + } + } +``` + +## Resolving JDBC Timeout on Large Inserts {#v07-resolving-jdbc-timeout-on-large-inserts} + +When performing large inserts in ClickHouse with long execution times, you may encounter JDBC timeout errors like: + +```plaintext +Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443] +``` + +These errors can disrupt the data insertion process and affect system stability. To address this issue you need to adjust a few timeout settings in the client's OS. + +### Mac OS {#v07-mac-os} + +On Mac OS, the following settings can be adjusted to resolve the issue: + +- `net.inet.tcp.keepidle`: 60000 +- `net.inet.tcp.keepintvl`: 45000 +- `net.inet.tcp.keepinit`: 45000 +- `net.inet.tcp.keepcnt`: 8 +- `net.inet.tcp.always_keepalive`: 1 + +### Linux {#v07-linux} + +On Linux, the equivalent settings alone may not resolve the issue. Additional steps are required due to the differences in how Linux handles socket keep-alive settings. Follow these steps: + +1. Adjust the following Linux kernel parameters in `/etc/sysctl.conf` or a related configuration file: + +- `net.inet.tcp.keepidle`: 60000 +- `net.inet.tcp.keepintvl`: 45000 +- `net.inet.tcp.keepinit`: 45000 +- `net.inet.tcp.keepcnt`: 8 +- `net.inet.tcp.always_keepalive`: 1 +- `net.ipv4.tcp_keepalive_intvl`: 75 +- `net.ipv4.tcp_keepalive_probes`: 9 +- `net.ipv4.tcp_keepalive_time`: 60 (You may consider lowering this value from the default 300 seconds) + +2. After modifying the kernel parameters, apply the changes by running the following command: + +```shell +sudo sysctl -p + ``` + +After Setting those settings, you need to ensure that your client enables the Keep Alive option on the socket: + +```java +properties.setProperty("socket_keepalive", "true"); +``` + + +Currently, you must use Apache HTTP Client library when setting the socket keep-alive, as the other two HTTP client libraries supported by `clickhouse-java` don't allow setting socket options. For a detailed guide, see [Configuring HTTP library](#v07-configuring-http-library). + + +Alternatively, you can add equivalent parameters to the JDBC URL. + +The default socket and connection timeout for the JDBC driver is 30 seconds. The timeout can be increased to support large data insert operations. Use the `options` method on `ClickHouseClient` together with the `SOCKET_TIMEOUT` and `CONNECTION_TIMEOUT` options as defined by `ClickHouseClientOption`: + +```java showLineNumbers +final int MS_12H = 12 * 60 * 60 * 1000; // 12 h in ms +final String sql = "insert into table_a (c1, c2, c3) select c1, c2, c3 from table_b;"; + +try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP)) { + client.read(servers).write() + .option(ClickHouseClientOption.SOCKET_TIMEOUT, MS_12H) + .option(ClickHouseClientOption.CONNECTION_TIMEOUT, MS_12H) + .query(sql) + .executeAndWait(); +} +``` + +
    diff --git a/docs/clickhouse-docs/navigation.json b/docs/clickhouse-docs/navigation.json new file mode 100644 index 000000000..b8fae20c4 --- /dev/null +++ b/docs/clickhouse-docs/navigation.json @@ -0,0 +1,12 @@ +{ + "expanded": true, + "group": "Java", + "icon": "/images/integrations/logos/java.svg", + "pages": [ + "integrations/language-clients/java/client", + "integrations/language-clients/java/jdbc", + "integrations/language-clients/java/r2dbc", + "integrations/language-clients/java/date-time-guide" + ], + "root": "integrations/language-clients/java/index" +} diff --git a/docs/clickhouse-docs/r2dbc.mdx b/docs/clickhouse-docs/r2dbc.mdx new file mode 100644 index 000000000..452ee9ff8 --- /dev/null +++ b/docs/clickhouse-docs/r2dbc.mdx @@ -0,0 +1,77 @@ +--- +sidebarTitle: 'R2DBC driver' +keywords: ['clickhouse', 'java', 'driver', 'integrate', 'r2dbc'] +description: 'ClickHouse R2DBC driver' +slug: /integrations/java/r2dbc +title: 'R2DBC driver' +doc_type: 'reference' +integration: + - support_level: 'core' + - category: 'language_client' +--- + +## R2DBC driver {#r2dbc-driver} + +[R2DBC](https://r2dbc.io/) wrapper of async Java client for ClickHouse. + +### Environment requirements {#environment-requirements} + +- [OpenJDK](https://openjdk.java.net) version >= 8 + +### Setup {#setup} + +```xml + + com.clickhouse + + clickhouse-r2dbc + 0.7.1 + + all + + + * + * + + + +``` + +### Connect to ClickHouse {#connect-to-clickhouse} + +```java showLineNumbers +ConnectionFactory connectionFactory = ConnectionFactories + .get("r2dbc:clickhouse:http://{username}:{password}@{host}:{port}/{database}"); + + Mono.from(connectionFactory.create()) + .flatMapMany(connection -> connection +``` + +### Query {#query} + +```java showLineNumbers +connection + .createStatement("select domain, path, toDate(cdate) as d, count(1) as count from clickdb.clicks where domain = :domain group by domain, path, d") + .bind("domain", domain) + .execute() + .flatMap(result -> result + .map((row, rowMetadata) -> String.format("%s%s[%s]:%d", row.get("domain", String.class), + row.get("path", String.class), + row.get("d", LocalDate.class), + row.get("count", Long.class))) + ) + .doOnNext(System.out::println) + .subscribe(); +``` + +### Insert {#insert} + +```java showLineNumbers +connection + .createStatement("insert into clickdb.clicks values (:domain, :path, :cdate, :count)") + .bind("domain", click.getDomain()) + .bind("path", click.getPath()) + .bind("cdate", LocalDateTime.now()) + .bind("count", 1) + .execute(); +```