diff --git a/Cargo.lock b/Cargo.lock
index 6078aff..c9a3868 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -177,6 +177,45 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "asn1-rs"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 2.0.21",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure 0.13.2",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
[[package]]
name = "astral-tokio-tar"
version = "0.6.4"
@@ -384,6 +423,15 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+[[package]]
+name = "bit-vec"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -1090,6 +1138,12 @@ dependencies = [
"parking_lot_core",
]
+[[package]]
+name = "data-encoding"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
+
[[package]]
name = "deadpool"
version = "0.12.3"
@@ -1148,6 +1202,20 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
+[[package]]
+name = "der-parser"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
[[package]]
name = "deranged"
version = "0.5.8"
@@ -1198,17 +1266,21 @@ dependencies = [
"figment",
"flate2",
"futures",
+ "hyper-rustls",
+ "hyper-util",
"indexmap 2.14.2",
"maxminddb",
"memchr",
"metrics",
"parking_lot",
"proptest",
+ "rcgen",
"rdkafka",
"regex",
"reqwest 0.12.28",
"reqwest 0.13.5",
"rustc-hash",
+ "rustls",
"scalo",
"schemars 1.2.2",
"serde",
@@ -2994,6 +3066,15 @@ dependencies = [
"objc2-foundation",
]
+[[package]]
+name = "oid-registry"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
+dependencies = [
+ "asn1-rs",
+]
+
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -3256,6 +3337,16 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "pem"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120"
+dependencies = [
+ "base64 0.23.1",
+ "serde_core",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -3979,6 +4070,20 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "rcgen"
+version = "0.14.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8"
+dependencies = [
+ "pem",
+ "ring",
+ "rustls-pki-types",
+ "time",
+ "x509-parser",
+ "yasna",
+]
+
[[package]]
name = "rdkafka"
version = "0.39.0"
@@ -4239,6 +4344,15 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "rustix"
version = "1.1.5"
@@ -4967,6 +5081,17 @@ dependencies = [
"futures-core",
]
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
[[package]]
name = "synstructure"
version = "0.14.0"
@@ -6360,6 +6485,24 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+[[package]]
+name = "x509-parser"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "ring",
+ "rusticata-macros",
+ "thiserror 2.0.21",
+ "time",
+]
+
[[package]]
name = "xattr"
version = "1.6.1"
@@ -6376,6 +6519,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+[[package]]
+name = "yasna"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
+dependencies = [
+ "bit-vec 0.9.1",
+ "time",
+]
+
[[package]]
name = "yoke"
version = "0.8.3"
@@ -6396,7 +6549,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
- "synstructure",
+ "synstructure 0.14.0",
]
[[package]]
@@ -6437,7 +6590,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
- "synstructure",
+ "synstructure 0.14.0",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 42a4e98..fda449e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -85,12 +85,17 @@ flate2 = ">=1.1.10, <2"
tar = ">=0.4.46, <0.5"
# ClickHouse crate (HyperI fork with native TCP + unified client)
-# rustls-tls: HTTPS for HTTP transport
+# rustls-tls + rustls-tls-native-roots: HTTPS for clients built with Client::default()
# native-transport + native-tls-rustls: native TCP with TLS
-# rustls-tls-native-roots: uses system trust store for internal CAs
clickhouse = { version = ">=0.15.0, <0.16", features = ["lz4", "time", "rustls-tls", "rustls-tls-native-roots", "tcp", "native-tls-rustls", "inserter"] }
time = ">=0.3.55, <0.4"
+# The HTTP client handed to clickhouse::Client::with_http_client: connect timeout,
+# idle-pool eviction and HTTPS verified against the system trust store.
+hyper-util = { version = ">=0.1.21, <0.2", features = ["client-legacy", "http1", "tokio"] }
+hyper-rustls = { version = ">=0.27.10, <0.28", default-features = false, features = ["http1", "tls12", "native-tokio", "aws-lc-rs"] }
+rustls = { version = ">=0.23.45, <0.24", default-features = false, features = ["std", "tls12", "aws_lc_rs"] }
+
# Utilities
uuid = { version = ">=1.27.0, <2", features = ["v4", "v7"] }
rustc-hash = ">=2.1.3, <3" # FxHashMap - faster hashing for internal maps
@@ -136,6 +141,9 @@ rdkafka = { version = ">=0.39.0, <0.40", features = ["dynamic-linking"] }
clickhouse = { version = ">=0.15.0, <0.16", features = ["lz4", "time", "rustls-tls", "rustls-tls-native-roots", "tcp", "native-tls-rustls", "inserter"] }
time = ">=0.3.55, <0.4"
+# Private CA and server certificates for the TLS ClickHouse tests
+rcgen = ">=0.14.10, <0.15"
+
# Async test utilities
tokio-test = ">=0.4.6, <0.5"
tempfile = ">=3.27.0, <4"
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 5f5d610..4538946 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -46,7 +46,7 @@ file drives the loader's own weights and is reported as a mismatch at startup.
| Setting | Values | Effect |
|---------|--------|--------|
-| `clickhouse.protocol` | `http` (default), `native` | HTTP (8123/8543). `native` is refused at startup: the pinned client has no TCP row fetch, and the native insert sink waits on clickhouse-rs#15 |
+| `clickhouse.protocol` | `http` (default), `native` | HTTP (8123), HTTPS (8443). `native` is refused at startup: the pinned client has no TCP row fetch, and the native insert sink waits on clickhouse-rs#15 |
| `clickhouse.insert_format` | `row_binary` (default), `json_each_row` | binary (server skips JSON parsing) vs self-describing JSON |
| `clickhouse.hosts` | list | multi-host failover (native pool round-robins, skips a refusing endpoint) |
| `clickhouse.database` | string, `dfe` (default) | database the connection targets -- DDL, schema reflection, and any table name that arrives without a `db.` prefix |
@@ -69,19 +69,24 @@ Both formats insert over HTTP -- see
### Transport security (TLS, private CAs)
-One trust mechanism covers both transports. By default a TLS connection trusts
-the OS native root store plus the compiled Mozilla (webpki) bundle. Internal
-clusters behind a private CA are supported by pointing at the CA PEM -- no proxy
-termination, no `InsecureSkipVerify`.
+HTTPS verifies the server's certificate and host name against the container's system trust store. A private CA is trusted by adding it to that store, or by pointing `SSL_CERT_FILE` (or `SSL_CERT_DIR`) at a bundle that holds it -- there is no CA path in the loader config and no way to turn verification off.
| Setting | Values | Effect |
|---------|--------|--------|
-| `clickhouse.tls` | bool | TLS for the connection (HTTPS 8543 / secure native 9440) |
-| `clickhouse.tls_ca_file` | path | private CA PEM (may bundle many certs; all are loaded). Augments native + webpki |
-| `clickhouse.tls_ca_exclusive` | bool | trust ONLY `tls_ca_file`, ignore native + webpki |
+| `clickhouse.tls.enabled` | bool | HTTPS for the connection. When `clickhouse.tls` is absent, a host on 8443/9440 or under `clickhouse.cloud` turns it on |
+| `clickhouse.tls.ca_cert_file`, `cert_file`, `key_file`, `skip_verify` | -- | refused at startup: no client reads them |
-A CA file with no usable certificate is a hard error (it never silently falls
-back to public roots). See [clickhouse/TLS.md](clickhouse/TLS.md).
+See [clickhouse/TLS.md](clickhouse/TLS.md).
+
+### ClickHouse timeouts
+
+| Setting | Default | Bounds |
+|---------|---------|--------|
+| `clickhouse.connect_timeout_ms` | `5000` | opening a TCP connection |
+| `clickhouse.request_timeout_ms` | `30000` | every query (schema reads, health), and an insert waiting to hand over its next chunk |
+| `clickhouse.insert_timeout_ms` | `60000` | an insert waiting for the server's answer once its rows are sent |
+
+A timed-out insert is held and sent again on a later flush, and its Kafka offset stays uncommitted until it lands. 60 s is about 270x the ClickHouse CPU cost of the largest default flush (20,000 rows at a measured 91,000 rows per CPU-second), and two rounds of timed-out concurrent inserts still fit Kafka's 300 s `max.poll.interval.ms`, which a blocked flush cycle counts against. Idle connections close on the client side after 2 s.
### Capture (what gets stored)
@@ -119,9 +124,9 @@ Config hot-reloads on change. Some fields are safe to apply live; some require a
restart and are logged as a warning rather than silently applied:
- **Restart-required:** `clickhouse.protocol`, ClickHouse URL/hosts,
- `clickhouse.insert_format`, TLS settings (`tls`, `tls_ca_file`,
- `tls_ca_exclusive`), Kafka connection -- all of these are baked into the
- `Client` at build time.
+ `clickhouse.insert_format`, `clickhouse.tls.enabled`, the
+ `clickhouse.*_timeout_ms` bounds, Kafka connection -- all of these are baked
+ into the `Client` at build time.
- **Safe to reload:** buffer thresholds, capture modes, enrichment toggles,
routing rules.
diff --git a/docs/clickhouse/CLICKHOUSE-EXT.md b/docs/clickhouse/CLICKHOUSE-EXT.md
index 0566bea..2d0020c 100644
--- a/docs/clickhouse/CLICKHOUSE-EXT.md
+++ b/docs/clickhouse/CLICKHOUSE-EXT.md
@@ -84,6 +84,8 @@ fork repo is now archived and read-only, so the branch can no longer move and
the pinned rev stays reachable. It also means no fix can land on the fork:
retiring the patch for crates.io `clickhouse` is the only way forward (#133).
+The HTTP client is the loader's own. `build_client` in `src/clickhouse/client_http.rs` hands the fork a hyper client through `Client::with_http_client`, carrying the pool timer that closes idle connections after 2 s, the connect timeout and HTTPS verified against the system trust store. Queries and inserts carry the request and insert timeouts on top. #133 still stands for the rest of the fork.
+
## What stays out of clickhouse_ext
- The transport, pool, handshake, and Native block framing -- those are the
diff --git a/docs/clickhouse/TLS.md b/docs/clickhouse/TLS.md
index dd8bb76..874ba3b 100644
--- a/docs/clickhouse/TLS.md
+++ b/docs/clickhouse/TLS.md
@@ -10,91 +10,52 @@
# TLS and private CAs
-ClickHouse connections can run over TLS on either transport: HTTPS (8543) for
-`transport = http`, secure native (9440) for `transport = native`. One trust
-mechanism serves both -- the loader does not carry two TLS code paths, and a
-private-CA cluster is trusted the same way regardless of transport.
+The loader reaches ClickHouse over HTTP (`clickhouse.protocol: http`; `native` is refused at startup). With TLS on it speaks HTTPS, usually on 8443, and verifies the server's certificate and host name against the container's system trust store.
```mermaid
-flowchart TB
- CFG["clickhouse.tls / tls_ca_file / tls_ca_exclusive"]
- TRUST["Trust anchor set"]
- NAT["OS native roots
(rustls-native-certs)"]
- WEB["Mozilla bundle
(webpki-roots)"]
- CA["Private CA PEM
(tls_ca_file)"]
- HTTP["HTTPS 8543"]
- NATIVE["secure native 9440"]
-
- CFG --> TRUST
- NAT --> TRUST
- WEB --> TRUST
- CA --> TRUST
- TRUST --> HTTP
- TRUST --> NATIVE
+flowchart LR
+ CFG["clickhouse.tls.enabled"]
+ STORE["System trust store
(image bundle, or SSL_CERT_FILE / SSL_CERT_DIR)"]
+ VERIFY["Certificate chain + host name check"]
+ HTTPS["HTTPS to ClickHouse"]
+
+ CFG --> HTTPS
+ STORE --> VERIFY
+ VERIFY --> HTTPS
```
-## Default trust
+## Turning TLS on
-With `tls = true` and no CA file, a TLS connection trusts the OS native root
-store plus the compiled-in Mozilla (webpki) bundle. Public clusters and any
-cluster whose CA is already installed in the host trust store work with no extra
-configuration.
+| Setting | Effect |
+|---------|--------|
+| `clickhouse.tls.enabled` | HTTPS for the connection |
+| `clickhouse.tls` absent | TLS turns on for a host on 8443 or 9440, or under `clickhouse.cloud` |
+
+The SNI name and the name checked against the certificate are the host in `clickhouse.hosts`, so connect by a name (or IP) the certificate lists.
## Private / internal CAs
-Internal clusters behind a private CA are trusted by pointing the loader at the
-CA certificate:
+A private CA is trusted the way the system trusts it: add it to the image's trust store, or point `SSL_CERT_FILE` at a PEM bundle that holds it (`SSL_CERT_DIR` at a directory of them). Either variable replaces the platform store rather than adding to it, so a bundle that must also trust public CAs has to carry them too.
-| Setting | Effect |
-|---------|--------|
-| `clickhouse.tls` | enable TLS for the connection |
-| `clickhouse.tls_ca_file` | path to a private CA PEM; augments native + webpki |
-| `clickhouse.tls_ca_exclusive` | trust ONLY `tls_ca_file`; ignore native + webpki |
-
-The CA file is read with `AppendCertsFromPEM` semantics (the clickhouse-go
-model): every PEM certificate block in the file is parsed best-effort and all
-valid certificates land in one trust store. A bundle of concatenated certs
-(`cat root.pem intermediate.pem > ca.pem`) is loaded in full -- roots and
-intermediates both become anchors, which covers servers that do not present
-their full chain and multi-tier PKI.
-
-A CA file that yields zero usable certificates, or an unreadable path, is a hard
-error. The loader never silently falls back to public roots when a CA file was
-asked for -- a misconfigured trust store fails the connection loudly rather than
-trusting the wrong anchors.
-
-## Augment vs exclusive
-
-- **Augment (default when `tls_ca_file` is set):** native + webpki + the private
- CA. The cluster's internal CA is trusted alongside public roots. Use this
- unless you have a reason to lock trust down.
-- **Exclusive (`tls_ca_exclusive = true`):** only the private CA. Native and
- webpki roots are ignored. Use this to pin trust to your internal PKI and
- refuse anything chaining to a public CA.
+A trust store with no readable CA certificate fails the client at startup instead of connecting unverified.
## What is deliberately not offered
-There is no insecure / skip-verify mode. Trusting an internal cluster is done by
-naming its CA, not by turning verification off. If you find yourself wanting to
-disable verification, supply the CA PEM instead -- that is the supported and
-auditable path.
-
-Client-certificate (mTLS) auth is out of scope today.
+`clickhouse.tls.ca_cert_file`, `cert_file`, `key_file` and `skip_verify` are refused at startup: no client reads them. There is no skip-verify mode, and client-certificate (mTLS) auth is out of scope today.
## Example: private-CA cluster
```yaml
clickhouse:
- transport: native # secure native protocol
- tls: true # 9440
- tls_ca_file: /etc/dfe/internal-ca.pem
+ protocol: http
+ tls:
+ enabled: true
hosts:
- - clickhouse.example.internal:9440
+ - clickhouse.example.internal:8443
+```
+
+```text
+SSL_CERT_FILE=/etc/dfe/ca-bundle.pem # the private CA plus any public roots still needed
```
-The same `tls_ca_file` works unchanged if you switch `transport` to `http`
-(8543) -- one trust config, both transports. The CA PEM is non-secret and is
-distributed with the deployment; the cluster password stays in the
-git-excluded environment, never in committed config. See
-[../CONFIGURATION.md](../CONFIGURATION.md) for the cascade and
-[../deployment/](../deployment/) for how secrets are supplied at runtime.
+The CA bundle is non-secret and is distributed with the deployment; the cluster password stays in the git-excluded environment, never in committed config. See [../CONFIGURATION.md](../CONFIGURATION.md) for the cascade and [../deployment/](../deployment/) for how secrets are supplied at runtime.
diff --git a/docs/config-schema.json b/docs/config-schema.json
index 1e81499..4343b69 100644
--- a/docs/config-schema.json
+++ b/docs/config-schema.json
@@ -60,7 +60,10 @@
"protocol": "http",
"insert_format": "rowbinary",
"tables": [],
- "tls": null
+ "tls": null,
+ "connect_timeout_ms": 5000,
+ "request_timeout_ms": 30000,
+ "insert_timeout_ms": 60000
}
},
"payload": {
@@ -695,6 +698,27 @@
}
],
"default": null
+ },
+ "connect_timeout_ms": {
+ "description": "Bound on opening a TCP connection to `ClickHouse`, in milliseconds.",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0,
+ "default": 5000
+ },
+ "request_timeout_ms": {
+ "description": "Bound on a query, and on an insert waiting to hand over its next chunk,\nin milliseconds.",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0,
+ "default": 30000
+ },
+ "insert_timeout_ms": {
+ "description": "Bound on an insert waiting for the server's answer once its rows are\nsent, in milliseconds. A timed-out batch is held and retried.",
+ "type": "integer",
+ "format": "uint64",
+ "minimum": 0,
+ "default": 60000
}
}
},
diff --git a/docs/config-schema.yaml b/docs/config-schema.yaml
index 3a4c019..785da94 100644
--- a/docs/config-schema.yaml
+++ b/docs/config-schema.yaml
@@ -78,6 +78,9 @@ properties:
insert_format: rowbinary
tables: []
tls: null
+ connect_timeout_ms: 5000
+ request_timeout_ms: 30000
+ insert_timeout_ms: 60000
payload:
description: Payload pipeline mode. **Restart required.**
$ref: '#/$defs/PayloadConfig'
@@ -617,6 +620,28 @@ $defs:
- $ref: '#/$defs/TlsConfig'
- type: 'null'
default: null
+ connect_timeout_ms:
+ description: Bound on opening a TCP connection to `ClickHouse`, in milliseconds.
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 5000
+ request_timeout_ms:
+ description: |-
+ Bound on a query, and on an insert waiting to hand over its next chunk,
+ in milliseconds.
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 30000
+ insert_timeout_ms:
+ description: |-
+ Bound on an insert waiting for the server's answer once its rows are
+ sent, in milliseconds. A timed-out batch is held and retried.
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 60000
InsertFormat:
description: |-
Insert format for data writes.
diff --git a/src/clickhouse/client_http.rs b/src/clickhouse/client_http.rs
index 20f34ae..9fdf57e 100644
--- a/src/clickhouse/client_http.rs
+++ b/src/clickhouse/client_http.rs
@@ -22,18 +22,31 @@
//! - Table existence checks
//! - Health checks (ping)
+use std::future::Future;
use std::sync::Arc;
+use std::time::Duration;
+use hyper_rustls::HttpsConnectorBuilder;
+use hyper_util::client::legacy::Client as HyperClient;
+use hyper_util::client::legacy::connect::HttpConnector;
+use hyper_util::rt::{TokioExecutor, TokioTimer};
use rustc_hash::FxHashMap;
use serde::Deserialize;
-use super::config::ClickHouseConfig;
+use super::config::{ClickHouseConfig, Timeouts};
use super::error::ClickHouseError;
use super::types::{ColumnInfo, ParsedType, TableSchema};
/// Result type for `ClickHouse` operations.
pub type Result = std::result::Result;
+/// TCP keepalive interval on `ClickHouse` connections.
+const TCP_KEEPALIVE: Duration = Duration::from_secs(60);
+
+/// Shorter than `ClickHouse`'s keep-alive timeout, so the client closes an idle
+/// connection before the server times it out and logs an error for it.
+const POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(2);
+
/// `ClickHouse` client for DDL, schema queries, and health checks.
///
/// Wraps a single `clickhouse::Client` (HTTP or TCP per config). Data inserts
@@ -44,6 +57,58 @@ pub struct ClickHouseQueryClient {
ch_client: clickhouse::Client,
/// Database name.
database: String,
+ /// Bounds on each query and insert.
+ timeouts: Timeouts,
+}
+
+/// Run `request`, failing it with `TimedOut` once `bound` passes.
+pub(crate) async fn bounded(
+ bound: Duration,
+ request: impl Future