clickhouse-tool 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. clickhouse_tool-0.1.1/.gitignore +7 -0
  2. clickhouse_tool-0.1.1/LICENSE +21 -0
  3. clickhouse_tool-0.1.1/PKG-INFO +52 -0
  4. clickhouse_tool-0.1.1/README.md +24 -0
  5. clickhouse_tool-0.1.1/SKILL.md +234 -0
  6. clickhouse_tool-0.1.1/config.example.toml +40 -0
  7. clickhouse_tool-0.1.1/pyproject.toml +149 -0
  8. clickhouse_tool-0.1.1/src/clickhouse_tool/__about__.py +1 -0
  9. clickhouse_tool-0.1.1/src/clickhouse_tool/__init__.py +5 -0
  10. clickhouse_tool-0.1.1/src/clickhouse_tool/__main__.py +6 -0
  11. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/__init__.py +0 -0
  12. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/__init__.py +0 -0
  13. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/config.py +21 -0
  14. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/facets.py +132 -0
  15. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/listing.py +45 -0
  16. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/logs.py +72 -0
  17. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/metrics.py +204 -0
  18. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/query.py +63 -0
  19. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/commands/tables.py +25 -0
  20. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/main.py +66 -0
  21. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/runner.py +141 -0
  22. clickhouse_tool-0.1.1/src/clickhouse_tool/cli/wiring.py +95 -0
  23. clickhouse_tool-0.1.1/src/clickhouse_tool/core/__init__.py +0 -0
  24. clickhouse_tool-0.1.1/src/clickhouse_tool/core/client/__init__.py +0 -0
  25. clickhouse_tool-0.1.1/src/clickhouse_tool/core/client/base.py +34 -0
  26. clickhouse_tool-0.1.1/src/clickhouse_tool/core/client/factory.py +66 -0
  27. clickhouse_tool-0.1.1/src/clickhouse_tool/core/client/http.py +95 -0
  28. clickhouse_tool-0.1.1/src/clickhouse_tool/core/client/native.py +186 -0
  29. clickhouse_tool-0.1.1/src/clickhouse_tool/core/config.py +265 -0
  30. clickhouse_tool-0.1.1/src/clickhouse_tool/core/errors.py +126 -0
  31. clickhouse_tool-0.1.1/src/clickhouse_tool/core/exit_codes.py +25 -0
  32. clickhouse_tool-0.1.1/src/clickhouse_tool/core/humanize.py +58 -0
  33. clickhouse_tool-0.1.1/src/clickhouse_tool/core/logging.py +67 -0
  34. clickhouse_tool-0.1.1/src/clickhouse_tool/core/models.py +55 -0
  35. clickhouse_tool-0.1.1/src/clickhouse_tool/core/monitoring.py +32 -0
  36. clickhouse_tool-0.1.1/src/clickhouse_tool/core/ssm.py +77 -0
  37. clickhouse_tool-0.1.1/src/clickhouse_tool/core/timezone.py +41 -0
  38. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/__init__.py +47 -0
  39. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/base.py +54 -0
  40. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/json.py +43 -0
  41. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/ndjson.py +31 -0
  42. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/table.py +158 -0
  43. clickhouse_tool-0.1.1/src/clickhouse_tool/formatters/toon.py +70 -0
  44. clickhouse_tool-0.1.1/src/clickhouse_tool/query/__init__.py +0 -0
  45. clickhouse_tool-0.1.1/src/clickhouse_tool/query/builder.py +426 -0
  46. clickhouse_tool-0.1.1/src/clickhouse_tool/query/columns.py +34 -0
  47. clickhouse_tool-0.1.1/src/clickhouse_tool/query/intents.py +91 -0
  48. clickhouse_tool-0.1.1/src/clickhouse_tool/query/predicates.py +92 -0
  49. clickhouse_tool-0.1.1/src/clickhouse_tool/query/sort.py +44 -0
  50. clickhouse_tool-0.1.1/src/clickhouse_tool/query/tables.py +46 -0
  51. clickhouse_tool-0.1.1/src/clickhouse_tool/query/timerange.py +91 -0
  52. clickhouse_tool-0.1.1/tests/cli/__init__.py +0 -0
  53. clickhouse_tool-0.1.1/tests/cli/test_config.py +57 -0
  54. clickhouse_tool-0.1.1/tests/cli/test_facets.py +114 -0
  55. clickhouse_tool-0.1.1/tests/cli/test_listing.py +66 -0
  56. clickhouse_tool-0.1.1/tests/cli/test_logs.py +225 -0
  57. clickhouse_tool-0.1.1/tests/cli/test_main.py +96 -0
  58. clickhouse_tool-0.1.1/tests/cli/test_metrics.py +201 -0
  59. clickhouse_tool-0.1.1/tests/cli/test_query.py +80 -0
  60. clickhouse_tool-0.1.1/tests/cli/test_regression.py +249 -0
  61. clickhouse_tool-0.1.1/tests/cli/test_runner.py +282 -0
  62. clickhouse_tool-0.1.1/tests/cli/test_tables.py +59 -0
  63. clickhouse_tool-0.1.1/tests/cli/test_wiring.py +97 -0
  64. clickhouse_tool-0.1.1/tests/client/__init__.py +0 -0
  65. clickhouse_tool-0.1.1/tests/client/test_base.py +45 -0
  66. clickhouse_tool-0.1.1/tests/client/test_error_translation.py +87 -0
  67. clickhouse_tool-0.1.1/tests/client/test_factory.py +115 -0
  68. clickhouse_tool-0.1.1/tests/client/test_http.py +156 -0
  69. clickhouse_tool-0.1.1/tests/client/test_native.py +228 -0
  70. clickhouse_tool-0.1.1/tests/config/__init__.py +0 -0
  71. clickhouse_tool-0.1.1/tests/config/test_config.py +173 -0
  72. clickhouse_tool-0.1.1/tests/config/test_ssm.py +149 -0
  73. clickhouse_tool-0.1.1/tests/conftest.py +66 -0
  74. clickhouse_tool-0.1.1/tests/e2e/__init__.py +0 -0
  75. clickhouse_tool-0.1.1/tests/e2e/test_exit_codes.py +181 -0
  76. clickhouse_tool-0.1.1/tests/formatters/__init__.py +0 -0
  77. clickhouse_tool-0.1.1/tests/formatters/conftest.py +28 -0
  78. clickhouse_tool-0.1.1/tests/formatters/test_base.py +69 -0
  79. clickhouse_tool-0.1.1/tests/formatters/test_json.py +68 -0
  80. clickhouse_tool-0.1.1/tests/formatters/test_ndjson.py +48 -0
  81. clickhouse_tool-0.1.1/tests/formatters/test_registry.py +30 -0
  82. clickhouse_tool-0.1.1/tests/formatters/test_table.py +155 -0
  83. clickhouse_tool-0.1.1/tests/formatters/test_toon.py +84 -0
  84. clickhouse_tool-0.1.1/tests/formatters/test_toon_hostile.py +85 -0
  85. clickhouse_tool-0.1.1/tests/formatters/test_zero_result_conformance.py +39 -0
  86. clickhouse_tool-0.1.1/tests/integration/__init__.py +0 -0
  87. clickhouse_tool-0.1.1/tests/integration/conftest.py +93 -0
  88. clickhouse_tool-0.1.1/tests/integration/test_backends.py +55 -0
  89. clickhouse_tool-0.1.1/tests/query/__init__.py +0 -0
  90. clickhouse_tool-0.1.1/tests/query/test_builder.py +424 -0
  91. clickhouse_tool-0.1.1/tests/query/test_columns.py +37 -0
  92. clickhouse_tool-0.1.1/tests/query/test_intents.py +48 -0
  93. clickhouse_tool-0.1.1/tests/query/test_predicates.py +123 -0
  94. clickhouse_tool-0.1.1/tests/query/test_purity.py +49 -0
  95. clickhouse_tool-0.1.1/tests/query/test_sort.py +54 -0
  96. clickhouse_tool-0.1.1/tests/query/test_tables.py +37 -0
  97. clickhouse_tool-0.1.1/tests/query/test_timerange.py +76 -0
  98. clickhouse_tool-0.1.1/tests/test_errors.py +93 -0
  99. clickhouse_tool-0.1.1/tests/test_exit_codes.py +35 -0
  100. clickhouse_tool-0.1.1/tests/test_humanize.py +60 -0
  101. clickhouse_tool-0.1.1/tests/test_logging.py +65 -0
  102. clickhouse_tool-0.1.1/tests/test_models.py +51 -0
  103. clickhouse_tool-0.1.1/tests/test_monitoring.py +41 -0
  104. clickhouse_tool-0.1.1/tests/test_package.py +29 -0
  105. clickhouse_tool-0.1.1/tests/test_timezone.py +49 -0
  106. clickhouse_tool-0.1.1/uv.lock +1340 -0
@@ -0,0 +1,7 @@
1
+ coverage.xml
2
+ htmlcov/
3
+ .coverage
4
+ .pytest_cache/
5
+ __pycache__/
6
+ *.pyc
7
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vlad Korolev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.5
2
+ Name: clickhouse-tool
3
+ Version: 0.1.1
4
+ Summary: ClickHouse query and administration CLI tool for the OTel logs/metrics pilot
5
+ Author-email: Vlad Korolev <vlad@v-lad.org>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: boto3>=1.34.0
9
+ Requires-Dist: pydantic>=2.5.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: sentry-sdk>=1.40.0
12
+ Requires-Dist: structlog>=24.0
13
+ Requires-Dist: typer>=0.12.0
14
+ Provides-Extra: all
15
+ Requires-Dist: clickhouse-connect>=0.7.0; extra == 'all'
16
+ Requires-Dist: clickhouse-driver>=0.2.9; extra == 'all'
17
+ Provides-Extra: dev
18
+ Requires-Dist: mypy>=1.13; extra == 'dev'
19
+ Requires-Dist: pre-commit>=4.0; extra == 'dev'
20
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.8; extra == 'dev'
23
+ Provides-Extra: http
24
+ Requires-Dist: clickhouse-connect>=0.7.0; extra == 'http'
25
+ Provides-Extra: native
26
+ Requires-Dist: clickhouse-driver>=0.2.9; extra == 'native'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # clickhouse-tool
30
+
31
+ A command-line client for querying and inspecting ClickHouse, over both
32
+ the native protocol (port 9000) and HTTP (port 8123).
33
+
34
+ `clickhouse-tool` provides porcelain commands (`logs`, `hosts`, `services`)
35
+ for browsing OTel-shaped log data, plus low-level verbs (`query`, `list`,
36
+ `lift`, `config`) for direct inspection of any table. Output renders as a
37
+ table, JSON, NDJSON, or the compact TOON format, with pinned typed exit
38
+ codes for scripting.
39
+
40
+ ## Install
41
+
42
+ ```
43
+ pip install clickhouse-tool[native] # native protocol (port 9000)
44
+ pip install clickhouse-tool[http] # HTTP protocol (port 8123)
45
+ pip install clickhouse-tool[all] # both
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ Copy `config.example.toml` to `./clickhouse-tool.toml` or
51
+ `~/.config/clickhouse-tool/config.toml` and fill in a profile for your
52
+ cluster. See that file for the full set of options.
@@ -0,0 +1,24 @@
1
+ # clickhouse-tool
2
+
3
+ A command-line client for querying and inspecting ClickHouse, over both
4
+ the native protocol (port 9000) and HTTP (port 8123).
5
+
6
+ `clickhouse-tool` provides porcelain commands (`logs`, `hosts`, `services`)
7
+ for browsing OTel-shaped log data, plus low-level verbs (`query`, `list`,
8
+ `lift`, `config`) for direct inspection of any table. Output renders as a
9
+ table, JSON, NDJSON, or the compact TOON format, with pinned typed exit
10
+ codes for scripting.
11
+
12
+ ## Install
13
+
14
+ ```
15
+ pip install clickhouse-tool[native] # native protocol (port 9000)
16
+ pip install clickhouse-tool[http] # HTTP protocol (port 8123)
17
+ pip install clickhouse-tool[all] # both
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ Copy `config.example.toml` to `./clickhouse-tool.toml` or
23
+ `~/.config/clickhouse-tool/config.toml` and fill in a profile for your
24
+ cluster. See that file for the full set of options.
@@ -0,0 +1,234 @@
1
+ # clickhouse-tool
2
+
3
+ CLI to query and inspect ClickHouse, over both the native protocol
4
+ (port 9000) and HTTP (port 8123). Invoked as `clickhouse-tool`.
5
+
6
+ ## When to Use / When NOT to Use
7
+
8
+ | Use clickhouse-tool for | Do NOT use for |
9
+ |--------------------------|-----------------|
10
+ | Browsing OTel-shaped logs with a `logs`/`hosts`/`services` porcelain | Writes or DDL (the tool is read-only by design) |
11
+ | Counting hosts/services over logs or metrics activity | Detailed metric values/time series (`hosts`/`services` only count activity, not metric values) |
12
+ | Inspecting any allow-listed table's rows/columns | Cluster administration (node/shard management, replication control) |
13
+ | Running direct SQL against the cluster | |
14
+ | Scripting against pinned exit codes | |
15
+
16
+ ## Quick Reference
17
+
18
+ ```bash
19
+ clickhouse-tool logs --service payments -l error -t 1h # recent error logs for a service
20
+ clickhouse-tool logs --follow --host k8s-3 # tail new rows continuously
21
+ clickhouse-tool hosts -t 15m # host counts, last 15 minutes
22
+ clickhouse-tool services --unbounded # every service, no cap
23
+ clickhouse-tool metrics names -n 10 # most active metric names
24
+ clickhouse-tool metrics status k8s.node.memory -t 1h # min/max/avg/stddev per metric
25
+ clickhouse-tool metrics histogram system.cpu.utilization -t 1h # time-bucketed stats
26
+ clickhouse-tool tables # every table list/lift/query may target
27
+ clickhouse-tool list otel.otel_logs -n 5 # peek at raw rows
28
+ clickhouse-tool lift otel.otel_logs # describe columns
29
+ clickhouse-tool query "SELECT count() FROM otel.otel_logs" # raw SQL
30
+ clickhouse-tool config # show resolved profile, no AWS needed
31
+ ```
32
+
33
+ ## Commands
34
+
35
+ ### `logs` — the baseline verb
36
+
37
+ ```bash
38
+ clickhouse-tool logs [--host/-h HOST] [--service SERVICE] [--namespace NS]
39
+ [--container C] [--level/-l LEVEL] [-t/--time-range 15m|2h|1d]
40
+ [--search/-s TERM] [--sort SORT] [--full] [--raw]
41
+ [--count] [--follow] [--utc] [-n/--limit N]
42
+ [-f/--format table|json|ndjson|toon]
43
+ ```
44
+
45
+ - `--count` returns a single row count instead of rows.
46
+ - `--full`/`--raw` select every column and switch to nested output — the only
47
+ verb that may stream (via the `ndjson` formatter).
48
+ - `--follow` polls continuously for new rows, oldest first, until Ctrl-C.
49
+ `-t` seeds the starting point (how far back the first batch reaches);
50
+ after that, only rows newer than the last one seen are printed. Not
51
+ compatible with `--count` or `--sort` (follow mode always orders
52
+ ascending by time).
53
+ - The `ts` column renders in local time by default; `--utc` shows UTC
54
+ instead. The profile's `default_timezone` sets the non-`--utc` default
55
+ (`local`, `UTC`, or any IANA zone name — `local` resolves to the
56
+ machine's own zone). `--full`/`--raw` show the raw server value
57
+ unconverted, since they select `*` directly, not the `ts` alias.
58
+ - Time window defaults to the profile's `default_window` (`2h` in the
59
+ example config) when `-t` is omitted.
60
+
61
+ ### `hosts` / `services` — grouped counts
62
+
63
+ ```bash
64
+ clickhouse-tool hosts [--namespace NS] [--container C] [--level/-l LEVEL]
65
+ [-t/--time-range WINDOW] [--search/-s TERM]
66
+ [--stream logs|metrics] [-n/--limit N] [--unbounded]
67
+ [-f/--format ...]
68
+ clickhouse-tool services [same flags]
69
+ ```
70
+
71
+ - Grouped by host or service, ordered by count descending, capped at 200
72
+ rows unless `--unbounded`. Each row also shows `last_seen`, rendered as a
73
+ relative time (`2h 20m ago`) -- a second unit only above a 10 threshold,
74
+ seconds is the finest granularity shown.
75
+ - `--stream metrics` (default) counts metric datapoints -- useful since not
76
+ every host/service that emits metrics also emits logs. `--stream logs`
77
+ counts log rows instead. Metrics mode only supports `-t`/`--time-range`;
78
+ `--namespace`, `--container`, `--level`, `--search` all raise `ConfigError`
79
+ (exit 3) on that stream, since metrics carry none of those attributes.
80
+
81
+ ### `metrics` — metric names, stats, values, and histograms
82
+
83
+ ```bash
84
+ clickhouse-tool metrics names [-t/--time-range WINDOW] [-n/--limit N] [--unbounded] [-f/--format ...]
85
+ clickhouse-tool metrics status <name> [--host H] [--service S] [-t/--time-range WINDOW]
86
+ [--split-by host,service] [-f/--format ...]
87
+ clickhouse-tool metrics values <name> [--host H] [--service S] [-t/--time-range WINDOW]
88
+ [--follow] [-n/--limit N] [-f/--format ...]
89
+ clickhouse-tool metrics histogram <name> [--host H] [--service S] [-t/--time-range WINDOW]
90
+ [--bucket WIDTH] [--split-by host,service] [-f/--format ...]
91
+ ```
92
+
93
+ - `<name>` matches an exact metric name (`k8s.node.filesystem.capacity`) or a
94
+ dotted stem matching every child metric (`k8s.node.memory`).
95
+ - `names` groups by metric name over the metrics stream, ordered by
96
+ datapoint count descending, capped at 200 unless `--unbounded`. Same
97
+ `last_seen` column as `hosts`/`services`.
98
+ - `status` reports `min`/`max`/`avg`/`stddev` per matched metric name.
99
+ `--split-by host,service` breaks the summary out per host and/or
100
+ service instead of merging them.
101
+ - `values` returns raw datapoints oldest-first, capped at 1000 unless
102
+ `-n` overrides. `--follow` polls continuously starting from the most
103
+ recent matching datapoint (not `-n`-compatible), same semantics as
104
+ `logs --follow`.
105
+ - `histogram` buckets datapoints by time, reporting `min`/`max`/`avg`/`count`
106
+ per bucket per matched metric name. `--bucket` sets the width explicitly
107
+ (`5m`, `1h`, ...); omitted, it's sized automatically from the time window
108
+ to land around 36 buckets, snapped to a round duration (seconds through
109
+ weeks). `--split-by host,service` breaks each metric's buckets out per
110
+ host and/or service instead of merging them.
111
+
112
+ ### `list` / `lift` — low-level table inspection
113
+
114
+ ```bash
115
+ clickhouse-tool list <table> [-n/--limit N] [-f/--format ...] # raw rows
116
+ clickhouse-tool lift <table> [-f/--format ...] # column metadata
117
+ ```
118
+
119
+ - `<table>` must be allow-listed: any `otel.<identifier>`, or one of
120
+ `system.parts`, `.errors`, `.tables`, `.columns`, `.metrics`,
121
+ `.asynchronous_metrics`, `.query_log`.
122
+ - `lift` defaults to `json` output (nested-friendly), not the profile's
123
+ `default_format`.
124
+
125
+ ### `query` — raw SQL
126
+
127
+ ```bash
128
+ clickhouse-tool query "SELECT ... " [--param name=value ...] [-f/--format ...]
129
+ echo "SELECT 1" | clickhouse-tool query
130
+ ```
131
+
132
+ - SQL comes from the positional argument, or from stdin if no argument is
133
+ given and stdin is not a TTY — never both at once.
134
+ - `--param` is repeatable, binds named parameters as strings.
135
+ - Bypasses the query builder entirely: no table allow-list, no column
136
+ validation. Read-only safety comes **solely** from the server-side
137
+ `readonly=2` setting, not from anything this tool parses. Server errors
138
+ (including malformed SQL) echo back verbatim — the caller wrote the query.
139
+
140
+ ### `config` — show the resolved profile
141
+
142
+ ```bash
143
+ clickhouse-tool config [--profile NAME]
144
+ ```
145
+
146
+ - Never touches AWS SSM and never opens a connection — works with zero AWS
147
+ credentials present. Secrets print as unresolved references
148
+ (`(unresolved, ssm:<path>)`), never as values.
149
+
150
+ ### `tables` — list what `list`/`lift`/`query` may target
151
+
152
+ ```bash
153
+ clickhouse-tool tables [--profile NAME] [-f/--format ...]
154
+ ```
155
+
156
+ - Combines the profile's `otel.*` tables (queried live from the cluster)
157
+ with the seven fixed `system.*` tables the allow-list also accepts.
158
+ Unlike `config`, this connects to the cluster.
159
+
160
+ ## Profiles and Connection
161
+
162
+ - `--profile NAME` on any command selects a named profile from config.
163
+ - Config precedence, highest first: CLI flag > `CLICKHOUSE_TOOL_*` env var >
164
+ selected profile > project-local `./clickhouse-tool.toml` > user
165
+ `~/.config/clickhouse-tool/config.toml` > built-in defaults.
166
+ - Copy `config.example.toml` to get started; it ships a `pilot` (native,
167
+ port 9000) and `pilot-http` (HTTP, port 8123) profile pointed at the same
168
+ cluster, with credentials resolved lazily from AWS SSM (`user`/`password`
169
+ as `{ ssm = "<path>" }`).
170
+ - Secrets are resolved lazily — only verbs that actually connect touch SSM.
171
+ `config` never does.
172
+
173
+ ## Output Formats
174
+
175
+ | Flag | Use case |
176
+ |------|----------|
177
+ | `-f json` | Agent processing, or nested output (`lift`, `--full`, `--raw`) — buffered array, Map columns kept nested |
178
+ | `-f ndjson` | Streaming large result sets row-by-row — never buffers the full set |
179
+ | `-f table` | Presenting results to a human in a terminal — rich table, null renders `-` |
180
+ | `-f toon` | Default format — compact `name[N]{fields}` dialect, cheapest to read/grep |
181
+
182
+ Format defaults to the profile's `default_format` (`toon` in the example
183
+ config) when `-f` is omitted, except `lift`, which defaults to `json`.
184
+
185
+ `-f table` humanizes numbers and timestamps; every other format renders raw
186
+ values, unaffected by any of the flags below:
187
+ - Numbers get a k/m/g suffix once the scaled value would read 10 or
188
+ higher (`1024` stays `1024`, `10000` becomes `10k`), rounded to 2
189
+ decimal places by default -- override with `--decimals N` (`metrics
190
+ status`/`values`/`histogram`).
191
+ - Timestamps drop their seconds component once the queried time window
192
+ exceeds 3 hours, showing whole seconds below that. `--ts-decimals N`
193
+ (`metrics values`/`histogram`) forces second-level display with `N`
194
+ fractional digits, overriding the window-based default.
195
+
196
+ ## Error Handling
197
+
198
+ | Exit code | Meaning | Agent action |
199
+ |-----------|---------|---------------|
200
+ | 0 | Success | Proceed |
201
+ | 1 | General/unexpected error | Report — no documented outcome maps here |
202
+ | 2 | Usage error | Fix command syntax |
203
+ | 3 | Config error (bad profile, unknown column/table/sort, malformed `--param`, missing driver, bad `--stream` value) | Check flags/profile, fix and retry |
204
+ | 4 | Connection failed | Cluster unreachable — check host/port/transport |
205
+ | 5 | Auth failed | Check credentials / SSM refs |
206
+ | 6 | Query timeout | Narrow the query or raise `query_timeout` in the profile |
207
+ | 7 | Result too large | Narrow the time window or add a `--limit` |
208
+ | 8 | Server resource error | Cluster is under memory/resource pressure — retry later |
209
+ | 9 | Permission denied | Check the ClickHouse user's grants |
210
+ | 10 | Schema error | Table/column doesn't exist as named — check `lift` |
211
+ | 11 | Unsupported filter (e.g. `--container` on a key that doesn't exist) | The container-preflight check failed — verify the filter value |
212
+ | 12 | Server error (unmapped ClickHouse code) | Server returned an error code with no local mapping — report the message |
213
+
214
+ Every typed error prints its message to stderr; stdout is reserved for
215
+ data only. `BrokenPipeError` (e.g. piping into `head`) exits 0 quietly,
216
+ no traceback.
217
+
218
+ ## Global Options
219
+
220
+ | Flag | Purpose |
221
+ |------|---------|
222
+ | `--profile NAME` | Select connection profile |
223
+ | `-f/--format table\|json\|ndjson\|toon` | Output format |
224
+ | `-v/--verbose` | Enable debug logging |
225
+ | `--help` | Show help (note: `-h` is reserved for `--host` on `logs`, not help) |
226
+
227
+ ## Known Limitations
228
+
229
+ - No cluster administration commands (node/shard/replication management)
230
+ — use ClickHouse's own tooling for that.
231
+ - `hosts --stream metrics` / `services --stream metrics` only enumerate
232
+ activity against `otel_metrics_gauge`; the other metrics tables
233
+ (`otel_metrics_sum`, `otel_metrics_histogram`, etc. — see `tables`) have
234
+ no porcelain support yet — use `list`/`query` directly against them.
@@ -0,0 +1,40 @@
1
+ # Example clickhouse-tool configuration.
2
+ #
3
+ # Copy to ./clickhouse-tool.toml (project-local) or
4
+ # ~/.config/clickhouse-tool/config.toml (user-level). Project-local wins
5
+ # on any field present in both.
6
+
7
+ default_profile = "pilot" # never "default"
8
+ default_format = "toon"
9
+ default_window = "2h"
10
+ default_timezone = "local" # "local", "UTC", or any IANA zone name
11
+
12
+ [profiles.pilot]
13
+ host = "your-clickhouse-host.example.com"
14
+ port = 9000
15
+ transport = "native" # "native" | "http", both implemented
16
+ database = "otel"
17
+ secure = false
18
+ logs_table = "otel_logs"
19
+ connect_timeout = 10
20
+ query_timeout = 60
21
+
22
+ [profiles.pilot.user]
23
+ ssm = "/clickhouse/v1/pilot/tool/username"
24
+ [profiles.pilot.password]
25
+ ssm = "/clickhouse/v1/pilot/tool/password"
26
+
27
+ [profiles.pilot-http] # HTTP-transport variant of the same pilot
28
+ host = "your-clickhouse-host.example.com"
29
+ port = 8123
30
+ transport = "http"
31
+ database = "otel"
32
+ secure = false
33
+ logs_table = "otel_logs"
34
+ connect_timeout = 10
35
+ query_timeout = 60
36
+
37
+ [profiles.pilot-http.user]
38
+ ssm = "/clickhouse/v1/pilot/tool/username"
39
+ [profiles.pilot-http.password]
40
+ ssm = "/clickhouse/v1/pilot/tool/password"
@@ -0,0 +1,149 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "clickhouse-tool"
7
+ version = "0.1.1"
8
+ description = "ClickHouse query and administration CLI tool for the OTel logs/metrics pilot"
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "Vlad Korolev", email = "vlad@v-lad.org"}
12
+ ]
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "typer>=0.12.0",
16
+ "pydantic>=2.5.0",
17
+ "rich>=13.0.0",
18
+ "structlog>=24.0",
19
+ "sentry-sdk>=1.40.0",
20
+ "boto3>=1.34.0"
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ native = [
25
+ "clickhouse-driver>=0.2.9"
26
+ ]
27
+ http = [
28
+ "clickhouse-connect>=0.7.0"
29
+ ]
30
+ all = [
31
+ "clickhouse-tool[native]",
32
+ "clickhouse-tool[http]"
33
+ ]
34
+ dev = [
35
+ "pytest>=8.0",
36
+ "pytest-cov>=4.0",
37
+ "ruff>=0.8",
38
+ "mypy>=1.13",
39
+ "pre-commit>=4.0"
40
+ ]
41
+
42
+ [project.scripts]
43
+ clickhouse-tool = "clickhouse_tool.cli.main:run"
44
+
45
+ [tool.coverage.report]
46
+ exclude_lines = [
47
+ "pragma: no cover",
48
+ "def __repr__",
49
+ "if self.debug:",
50
+ "if settings.DEBUG",
51
+ "raise AssertionError",
52
+ "raise NotImplementedError",
53
+ "if 0:",
54
+ "if __name__ == .__main__.:",
55
+ "class .*\\bProtocol\\):",
56
+ "@(abc\\.)?abstractmethod"
57
+ ]
58
+
59
+ [tool.coverage.run]
60
+ source = ["src"]
61
+ omit = [
62
+ "*/tests/*",
63
+ "*/test_*.py",
64
+ "*/__about__.py"
65
+ ]
66
+
67
+ [tool.hatch.build.targets.wheel]
68
+ packages = ["src/clickhouse_tool"]
69
+
70
+ [tool.mypy]
71
+ python_version = "3.11"
72
+ plugins = ["pydantic.mypy"]
73
+ strict = true
74
+ warn_return_any = true
75
+ warn_unused_configs = true
76
+ disallow_untyped_defs = true
77
+ disallow_any_generics = true
78
+ no_implicit_optional = true
79
+ warn_redundant_casts = true
80
+ warn_unused_ignores = true
81
+ warn_unreachable = true
82
+ strict_equality = true
83
+ exclude = ["tests/"]
84
+
85
+ [[tool.mypy.overrides]]
86
+ module = "tests.*"
87
+ disallow_untyped_defs = false
88
+
89
+ [[tool.mypy.overrides]]
90
+ module = ["clickhouse_driver.*", "clickhouse_connect.*"]
91
+ ignore_missing_imports = true
92
+
93
+ [tool.pytest.ini_options]
94
+ minversion = "7.0"
95
+ testpaths = ["tests"]
96
+ pythonpath = ["src"]
97
+ addopts = [
98
+ "--strict-markers",
99
+ "--strict-config",
100
+ "--cov=clickhouse_tool",
101
+ "--cov-report=term-missing",
102
+ "--cov-report=html:htmlcov",
103
+ "--cov-report=xml",
104
+ "--cov-fail-under=80"
105
+ ]
106
+ markers = [
107
+ "unit: marks tests as unit tests (fast, no external dependencies)",
108
+ "integration: marks tests as integration tests (requires the real ClickHouse pilot)",
109
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')"
110
+ ]
111
+ filterwarnings = [
112
+ "error",
113
+ "ignore::UserWarning",
114
+ "ignore::DeprecationWarning"
115
+ ]
116
+
117
+ [tool.ruff]
118
+ target-version = "py311"
119
+ line-length = 100
120
+
121
+ [tool.ruff.lint]
122
+ select = [
123
+ "E",
124
+ "F",
125
+ "I",
126
+ "N",
127
+ "W",
128
+ "UP",
129
+ "B",
130
+ "C4",
131
+ "SIM",
132
+ "PTH",
133
+ "RUF",
134
+ "S"
135
+ ]
136
+ ignore = [
137
+ "E501",
138
+ "B008",
139
+ "N818" # exception names are the plan's pinned contract (AuthFailed, QueryTimeout, ...)
140
+ ]
141
+ fixable = ["ALL"]
142
+ unfixable = []
143
+
144
+ [tool.ruff.lint.isort]
145
+ known-first-party = ["clickhouse_tool"]
146
+
147
+ [tool.ruff.lint.per-file-ignores]
148
+ "__init__.py" = ["F401"]
149
+ "tests/**/*.py" = ["S101"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from clickhouse_tool import __about__
2
+
3
+ __version__ = __about__.__version__
4
+
5
+ __all__ = ["__about__", "__version__"]
@@ -0,0 +1,6 @@
1
+ """Entry point for ``python -m clickhouse_tool``."""
2
+
3
+ from clickhouse_tool.cli.main import run
4
+
5
+ if __name__ == "__main__":
6
+ run()
@@ -0,0 +1,21 @@
1
+ """The skip-secrets verb: shows the resolved profile with provenance.
2
+
3
+ Never materialises secrets and never opens a connection, so it works
4
+ with no AWS credentials present at all.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+
11
+ import typer
12
+
13
+ from clickhouse_tool.cli import wiring
14
+
15
+
16
+ def config(
17
+ profile: str | None = typer.Option(None, "--profile", help="Named connection profile"),
18
+ ) -> None:
19
+ """Show the resolved configuration, with secrets redacted."""
20
+ resolved = wiring.load_profile(profile)
21
+ print(json.dumps(resolved.model_dump(), indent=2, default=str))