clickhouse-rb 0.1.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +40 -19
- data/lib/clickhouse/config.rb +4 -2
- data/lib/clickhouse/connection.rb +5 -4
- data/lib/clickhouse/http_transport.rb +6 -7
- data/lib/clickhouse/native_format_parser.rb +42 -14
- data/lib/clickhouse/null_instrumenter.rb +17 -0
- data/lib/clickhouse/response.rb +17 -25
- data/lib/clickhouse/transport_result.rb +2 -6
- data/lib/clickhouse/version.rb +1 -1
- data/lib/clickhouse-rb.rb +3 -0
- data/lib/clickhouse.rb +4 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3665b6f44472615341970c7b6169eaa3e54e7df0132ad866d24b8e24cb170df
|
|
4
|
+
data.tar.gz: b5948f2b40a4e969ed35daf92dfb24f3da23215fa113b86b168c3ab911f20335
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 777e4c17a2d3278fb7a1221ed16f5d4ea01f16eeaac31c5e24ba52f81a2b5dc81613ea379c1e722b9d75f55c3850f6761a1ad5e45265e3be4a2199449777f68c
|
|
7
|
+
data.tar.gz: af8ac7a565c217142e5234d4d83dc9caf70cf2aca86714985b76155ecf8434076f27b9716794f09090b392033a49a7b14097645bc8b180190d089df4e45616c5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Unreleased
|
|
2
|
+
|
|
3
|
+
## [0.3.0] - 2025-12-31
|
|
4
|
+
|
|
5
|
+
- Make responses more user-friendly ([#1](https://github.com/kukicola/clickhouse-rb/pull/1))
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2025-12-28
|
|
8
|
+
|
|
9
|
+
- Added instrumentation support with configurable `instrumenter` (defaults to `NullInstrumenter`)
|
|
10
|
+
- Query errors now raise `Clickhouse::QueryError` instead of returning a failed response
|
|
11
|
+
- Removed `Response#success?`, `Response#failure?`, and `Response#error` methods
|
|
2
12
|
|
|
3
13
|
## [0.1.0] - 2025-12-28
|
|
4
14
|
|
data/README.md
CHANGED
|
@@ -51,12 +51,10 @@ end
|
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
53
|
conn = Clickhouse::Connection.new
|
|
54
|
-
response = conn.query("SELECT
|
|
54
|
+
response = conn.query("SELECT id, name FROM users WHERE active = true")
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
puts row.inspect
|
|
59
|
-
end
|
|
56
|
+
response.each do |row|
|
|
57
|
+
puts "#{row[:id]}: #{row[:name]}"
|
|
60
58
|
end
|
|
61
59
|
```
|
|
62
60
|
|
|
@@ -85,24 +83,29 @@ end
|
|
|
85
83
|
|
|
86
84
|
### Working with Results
|
|
87
85
|
|
|
86
|
+
Response objects implement `Enumerable`, allowing direct iteration:
|
|
87
|
+
|
|
88
88
|
```ruby
|
|
89
89
|
response = conn.query("SELECT id, name, created_at FROM users")
|
|
90
90
|
|
|
91
|
+
# Iterate over rows as hashes with symbol keys
|
|
92
|
+
response.each { |row| puts row[:name] }
|
|
93
|
+
|
|
94
|
+
# Use any Enumerable method
|
|
95
|
+
response.map { |row| row[:id] }
|
|
96
|
+
response.select { |row| row[:id] > 10 }
|
|
97
|
+
response.first # => {id: 1, name: "Alice", created_at: 2024-01-01 00:00:00 UTC}
|
|
98
|
+
|
|
91
99
|
# Access raw rows (arrays)
|
|
92
100
|
response.rows # => [[1, "Alice", 2024-01-01 00:00:00 UTC], ...]
|
|
93
|
-
response.columns # => [
|
|
94
|
-
response.types # => [
|
|
101
|
+
response.columns # => [:id, :name, :created_at]
|
|
102
|
+
response.types # => [:UInt64, :String, :DateTime]
|
|
95
103
|
|
|
96
104
|
# Convert to array of hashes
|
|
97
|
-
response.to_a # => [{
|
|
98
|
-
|
|
99
|
-
# Check for errors
|
|
100
|
-
response.success? # => true
|
|
101
|
-
response.failure? # => false
|
|
102
|
-
response.error # => nil (or error message string)
|
|
105
|
+
response.to_a # => [{id: 1, name: "Alice", ...}, ...]
|
|
103
106
|
|
|
104
|
-
# Query summary from ClickHouse
|
|
105
|
-
response.summary # => {
|
|
107
|
+
# Query summary from ClickHouse (symbol keys)
|
|
108
|
+
response.summary # => {read_rows: "1", read_bytes: "42", ...}
|
|
106
109
|
```
|
|
107
110
|
|
|
108
111
|
### Query Parameters
|
|
@@ -150,14 +153,32 @@ response = conn.query(
|
|
|
150
153
|
| `connection_timeout` | `5` | Connection timeout in seconds |
|
|
151
154
|
| `pool_size` | `100` | Connection pool size |
|
|
152
155
|
| `pool_timeout` | `5` | Pool checkout timeout in seconds |
|
|
156
|
+
| `instrumenter` | `NullInstrumenter` | Instrumenter for query instrumentation |
|
|
153
157
|
|
|
154
|
-
##
|
|
158
|
+
## Instrumentation
|
|
159
|
+
|
|
160
|
+
You can instrument queries by providing an instrumenter that responds to `#instrument`:
|
|
155
161
|
|
|
156
162
|
```ruby
|
|
157
|
-
|
|
163
|
+
Clickhouse.configure do |config|
|
|
164
|
+
config.instrumenter = ActiveSupport::Notifications
|
|
165
|
+
end
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
# Subscribe to events
|
|
168
|
+
ActiveSupport::Notifications.subscribe("query.clickhouse") do |name, start, finish, id, payload|
|
|
169
|
+
puts "Query: #{payload[:sql]} took #{finish - start}s"
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The instrumenter receives event name `"query.clickhouse"` and payload `{sql: "..."}`.
|
|
174
|
+
|
|
175
|
+
## Error Handling
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
begin
|
|
179
|
+
conn.query("INVALID SQL")
|
|
180
|
+
rescue Clickhouse::QueryError => e
|
|
181
|
+
puts "Query failed: #{e.message}"
|
|
161
182
|
end
|
|
162
183
|
|
|
163
184
|
# Unsupported types raise an exception
|
data/lib/clickhouse/config.rb
CHANGED
|
@@ -21,7 +21,8 @@ module Clickhouse
|
|
|
21
21
|
password: "",
|
|
22
22
|
connection_timeout: 5,
|
|
23
23
|
pool_size: 100,
|
|
24
|
-
pool_timeout: 5
|
|
24
|
+
pool_timeout: 5,
|
|
25
|
+
instrumenter: NullInstrumenter.new
|
|
25
26
|
}.freeze
|
|
26
27
|
|
|
27
28
|
# @return [String] URL scheme (http or https)
|
|
@@ -33,7 +34,8 @@ module Clickhouse
|
|
|
33
34
|
# @return [Integer] Connection timeout in seconds
|
|
34
35
|
# @return [Integer] Connection pool size
|
|
35
36
|
# @return [Integer] Pool checkout timeout in seconds
|
|
36
|
-
|
|
37
|
+
# @return [#instrument] Instrumenter for query instrumentation
|
|
38
|
+
attr_accessor :scheme, :host, :port, :database, :username, :password, :connection_timeout, :pool_size, :pool_timeout, :instrumenter
|
|
37
39
|
|
|
38
40
|
# Creates a new configuration instance.
|
|
39
41
|
#
|
|
@@ -24,11 +24,12 @@ module Clickhouse
|
|
|
24
24
|
# @param options [Hash] query options
|
|
25
25
|
# @option options [Hash] :params query parameters
|
|
26
26
|
# @return [Response] query response with rows, columns, and metadata
|
|
27
|
+
# @raise [QueryError] if the query fails
|
|
27
28
|
def query(sql, options = {})
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
@config.instrumenter.instrument("query.clickhouse", {sql: sql}) do
|
|
30
|
+
result = @transport.execute(sql, options)
|
|
31
|
+
NativeFormatParser.new(result.body).parse.with(summary: result.summary)
|
|
32
|
+
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
end
|
|
@@ -30,18 +30,17 @@ module Clickhouse
|
|
|
30
30
|
# @param sql [String] SQL query to execute
|
|
31
31
|
# @param options [Hash] query options
|
|
32
32
|
# @option options [Hash] :params query parameters
|
|
33
|
-
# @return [TransportResult] result containing body
|
|
33
|
+
# @return [TransportResult] result containing body and summary
|
|
34
|
+
# @raise [QueryError] if the query fails
|
|
34
35
|
def execute(sql, options = {})
|
|
35
36
|
query_params = {database: @config.database}.merge(options[:params] || {})
|
|
36
37
|
response = @http_client.post("/", params: query_params, body: sql, headers: @default_headers)
|
|
37
38
|
|
|
38
|
-
summary = JSON.parse(response.headers["X-ClickHouse-Summary"])
|
|
39
|
+
summary = JSON.parse(response.headers["X-ClickHouse-Summary"], symbolize_names: true)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
TransportResult.new(success: false, body: nil, error: response.body.to_s, summary: summary)
|
|
44
|
-
end
|
|
41
|
+
raise QueryError, response.body.to_s unless response.status.success?
|
|
42
|
+
|
|
43
|
+
TransportResult.new(body: response.body, summary: summary)
|
|
45
44
|
end
|
|
46
45
|
end
|
|
47
46
|
end
|
|
@@ -46,8 +46,8 @@ module Clickhouse
|
|
|
46
46
|
col_type = read_string
|
|
47
47
|
|
|
48
48
|
if @columns.length < num_columns
|
|
49
|
-
@columns << col_name
|
|
50
|
-
@types << col_type
|
|
49
|
+
@columns << col_name.to_sym
|
|
50
|
+
@types << col_type.to_sym
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
columns_data << read_column(col_type, num_rows)
|
|
@@ -65,14 +65,14 @@ module Clickhouse
|
|
|
65
65
|
when "UInt16" then read_uint16_column(num_rows)
|
|
66
66
|
when "UInt32" then read_uint32_column(num_rows)
|
|
67
67
|
when "UInt64" then read_uint64_column(num_rows)
|
|
68
|
-
when "UInt128" then
|
|
69
|
-
when "UInt256" then
|
|
68
|
+
when "UInt128" then read_uint128_column(num_rows)
|
|
69
|
+
when "UInt256" then read_uint256_column(num_rows)
|
|
70
70
|
when "Int8" then read_int8_column(num_rows)
|
|
71
71
|
when "Int16" then read_int16_column(num_rows)
|
|
72
72
|
when "Int32" then read_int32_column(num_rows)
|
|
73
73
|
when "Int64" then read_int64_column(num_rows)
|
|
74
|
-
when "Int128" then
|
|
75
|
-
when "Int256" then
|
|
74
|
+
when "Int128" then read_int128_column(num_rows)
|
|
75
|
+
when "Int256" then read_int256_column(num_rows)
|
|
76
76
|
|
|
77
77
|
# Floats
|
|
78
78
|
when "Float32" then read_float32_column(num_rows)
|
|
@@ -82,7 +82,7 @@ module Clickhouse
|
|
|
82
82
|
when "Bool" then read_bool_column(num_rows)
|
|
83
83
|
|
|
84
84
|
# Strings
|
|
85
|
-
when "String" then
|
|
85
|
+
when "String" then read_string_column(num_rows)
|
|
86
86
|
when /^FixedString\((\d+)\)$/ then read_fixed_string_column($1.to_i, num_rows)
|
|
87
87
|
|
|
88
88
|
# Dates and Times
|
|
@@ -92,11 +92,11 @@ module Clickhouse
|
|
|
92
92
|
when /^DateTime64\((\d+)(?:,.*)?\)$/ then read_datetime64_column($1.to_i, num_rows)
|
|
93
93
|
|
|
94
94
|
# UUID
|
|
95
|
-
when "UUID" then
|
|
95
|
+
when "UUID" then read_uuid_column(num_rows)
|
|
96
96
|
|
|
97
97
|
# IP addresses
|
|
98
|
-
when "IPv4" then
|
|
99
|
-
when "IPv6" then
|
|
98
|
+
when "IPv4" then read_ipv4_column(num_rows)
|
|
99
|
+
when "IPv6" then read_ipv6_column(num_rows)
|
|
100
100
|
|
|
101
101
|
# Decimals - ClickHouse always returns Decimal(precision, scale)
|
|
102
102
|
when /^Decimal\((\d+),\s*(\d+)\)$/ then read_decimal_column($1.to_i, $2.to_i, num_rows)
|
|
@@ -145,6 +145,14 @@ module Clickhouse
|
|
|
145
145
|
@reader.read(num_rows * 8).unpack("Q<*")
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
def read_uint128_column(num_rows)
|
|
149
|
+
Array.new(num_rows) { read_le_bytes(16) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def read_uint256_column(num_rows)
|
|
153
|
+
Array.new(num_rows) { read_le_bytes(32) }
|
|
154
|
+
end
|
|
155
|
+
|
|
148
156
|
def read_int8_column(num_rows)
|
|
149
157
|
@reader.read(num_rows).unpack("c*")
|
|
150
158
|
end
|
|
@@ -161,6 +169,14 @@ module Clickhouse
|
|
|
161
169
|
@reader.read(num_rows * 8).unpack("q<*")
|
|
162
170
|
end
|
|
163
171
|
|
|
172
|
+
def read_int128_column(num_rows)
|
|
173
|
+
Array.new(num_rows) { read_signed_le_bytes(16) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def read_int256_column(num_rows)
|
|
177
|
+
Array.new(num_rows) { read_signed_le_bytes(32) }
|
|
178
|
+
end
|
|
179
|
+
|
|
164
180
|
def read_float32_column(num_rows)
|
|
165
181
|
@reader.read(num_rows * 4).unpack("e*")
|
|
166
182
|
end
|
|
@@ -173,6 +189,10 @@ module Clickhouse
|
|
|
173
189
|
@reader.read(num_rows).bytes.map { |b| b == 1 }
|
|
174
190
|
end
|
|
175
191
|
|
|
192
|
+
def read_string_column(num_rows)
|
|
193
|
+
Array.new(num_rows) { read_string }
|
|
194
|
+
end
|
|
195
|
+
|
|
176
196
|
def read_fixed_string_column(length, num_rows)
|
|
177
197
|
Array.new(num_rows) { @reader.read(length).force_encoding(Encoding::UTF_8) }
|
|
178
198
|
end
|
|
@@ -197,6 +217,18 @@ module Clickhouse
|
|
|
197
217
|
end
|
|
198
218
|
end
|
|
199
219
|
|
|
220
|
+
def read_uuid_column(num_rows)
|
|
221
|
+
Array.new(num_rows) { read_uuid }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def read_ipv4_column(num_rows)
|
|
225
|
+
Array.new(num_rows) { read_ipv4 }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def read_ipv6_column(num_rows)
|
|
229
|
+
Array.new(num_rows) { read_ipv6 }
|
|
230
|
+
end
|
|
231
|
+
|
|
200
232
|
def read_decimal_column(precision, scale, num_rows)
|
|
201
233
|
divisor = 10**scale
|
|
202
234
|
if precision <= 9
|
|
@@ -230,10 +262,6 @@ module Clickhouse
|
|
|
230
262
|
end
|
|
231
263
|
|
|
232
264
|
def read_uint64 = @reader.read(8).unpack1("Q<")
|
|
233
|
-
def read_uint128 = read_le_bytes(16)
|
|
234
|
-
def read_uint256 = read_le_bytes(32)
|
|
235
|
-
def read_int128 = read_signed_le_bytes(16)
|
|
236
|
-
def read_int256 = read_signed_le_bytes(32)
|
|
237
265
|
|
|
238
266
|
def read_uuid
|
|
239
267
|
first_half = @reader.read(8).bytes.reverse
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clickhouse
|
|
4
|
+
# Null instrumenter that does nothing.
|
|
5
|
+
# This is the default instrumenter used when no custom instrumenter is configured.
|
|
6
|
+
class NullInstrumenter
|
|
7
|
+
# Executes block without any instrumentation.
|
|
8
|
+
#
|
|
9
|
+
# @param _name [String] event name (ignored)
|
|
10
|
+
# @param _payload [Hash] event payload (ignored)
|
|
11
|
+
# @yield block to execute
|
|
12
|
+
# @return [Object] result of the block
|
|
13
|
+
def instrument(_name, _payload = {})
|
|
14
|
+
yield
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/clickhouse/response.rb
CHANGED
|
@@ -3,37 +3,29 @@
|
|
|
3
3
|
module Clickhouse
|
|
4
4
|
# Immutable response object containing query results.
|
|
5
5
|
#
|
|
6
|
-
# @example
|
|
6
|
+
# @example
|
|
7
7
|
# response = conn.query("SELECT id, name FROM users")
|
|
8
|
-
# response.
|
|
9
|
-
# response.columns # => ["id", "name"]
|
|
8
|
+
# response.columns # => [:id, :name]
|
|
10
9
|
# response.rows # => [[1, "Alice"], [2, "Bob"]]
|
|
11
|
-
# response.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Response = Data.define(:columns, :types, :rows, :error, :summary) do
|
|
18
|
-
# @param columns [Array<String>] column names
|
|
19
|
-
# @param types [Array<String>] column types
|
|
10
|
+
# response.each { |row| puts row[:name] }
|
|
11
|
+
Response = Data.define(:columns, :types, :rows, :summary) do
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
# @param columns [Array<Symbol>] column names
|
|
15
|
+
# @param types [Array<Symbol>] column types
|
|
20
16
|
# @param rows [Array<Array>] row data
|
|
21
|
-
# @param
|
|
22
|
-
|
|
23
|
-
def initialize(columns: [], types: [], rows: [], error: nil, summary: nil)
|
|
17
|
+
# @param summary [Hash, nil] ClickHouse query summary with symbol keys
|
|
18
|
+
def initialize(columns: [], types: [], rows: [], summary: nil)
|
|
24
19
|
super
|
|
25
20
|
end
|
|
26
21
|
|
|
27
|
-
#
|
|
28
|
-
# @
|
|
29
|
-
|
|
22
|
+
# Iterates over rows as hashes with symbol keys.
|
|
23
|
+
# @yield [Hash] each row as a hash
|
|
24
|
+
# @return [Enumerator] if no block given
|
|
25
|
+
def each
|
|
26
|
+
return to_enum(:each) unless block_given?
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def failure? = !success?
|
|
34
|
-
|
|
35
|
-
# Converts rows to an array of hashes.
|
|
36
|
-
# @return [Array<Hash>] rows as hashes with column names as keys
|
|
37
|
-
def to_a = rows.map { |row| columns.zip(row).to_h }
|
|
28
|
+
rows.each { |row| yield columns.zip(row).to_h }
|
|
29
|
+
end
|
|
38
30
|
end
|
|
39
31
|
end
|
|
@@ -4,13 +4,9 @@ module Clickhouse
|
|
|
4
4
|
# Immutable result from transport layer.
|
|
5
5
|
# @api private
|
|
6
6
|
#
|
|
7
|
-
# @!attribute [r] success
|
|
8
|
-
# @return [Boolean] true if request succeeded
|
|
9
7
|
# @!attribute [r] body
|
|
10
|
-
# @return [HTTP::Response::Body
|
|
11
|
-
# @!attribute [r] error
|
|
12
|
-
# @return [String, nil] error message for failed requests
|
|
8
|
+
# @return [HTTP::Response::Body] response body
|
|
13
9
|
# @!attribute [r] summary
|
|
14
10
|
# @return [Hash] ClickHouse query summary
|
|
15
|
-
TransportResult = Data.define(:
|
|
11
|
+
TransportResult = Data.define(:body, :summary)
|
|
16
12
|
end
|
data/lib/clickhouse/version.rb
CHANGED
data/lib/clickhouse.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "clickhouse/version"
|
|
4
|
+
require_relative "clickhouse/null_instrumenter"
|
|
4
5
|
require_relative "clickhouse/config"
|
|
5
6
|
require_relative "clickhouse/transport_result"
|
|
6
7
|
require_relative "clickhouse/http_transport"
|
|
@@ -28,6 +29,9 @@ module Clickhouse
|
|
|
28
29
|
# Base error class for all Clickhouse errors
|
|
29
30
|
class Error < StandardError; end
|
|
30
31
|
|
|
32
|
+
# Raised when a query fails (syntax error, unknown table, etc.)
|
|
33
|
+
class QueryError < Error; end
|
|
34
|
+
|
|
31
35
|
# Raised when encountering an unsupported ClickHouse data type
|
|
32
36
|
class UnsupportedTypeError < Error; end
|
|
33
37
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clickhouse-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Karol Bąk
|
|
@@ -63,12 +63,14 @@ files:
|
|
|
63
63
|
- ".standard.yml"
|
|
64
64
|
- CHANGELOG.md
|
|
65
65
|
- README.md
|
|
66
|
+
- lib/clickhouse-rb.rb
|
|
66
67
|
- lib/clickhouse.rb
|
|
67
68
|
- lib/clickhouse/buffered_reader.rb
|
|
68
69
|
- lib/clickhouse/config.rb
|
|
69
70
|
- lib/clickhouse/connection.rb
|
|
70
71
|
- lib/clickhouse/http_transport.rb
|
|
71
72
|
- lib/clickhouse/native_format_parser.rb
|
|
73
|
+
- lib/clickhouse/null_instrumenter.rb
|
|
72
74
|
- lib/clickhouse/pool.rb
|
|
73
75
|
- lib/clickhouse/response.rb
|
|
74
76
|
- lib/clickhouse/transport_result.rb
|