clickhouse-rb 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ccbd22754bea20c9ca9fed064ef2b4e7a5cf936b9e6c8c3f819f732adfc25e7
4
- data.tar.gz: 9537ffae5678fa0bf7ffbdd7b0cbb505aa66a8471072271765e98e99456a7f9d
3
+ metadata.gz: 86a862148595c1c9794a3d122e750db7959f5d4478b9dc3f65ee0f00319c55c0
4
+ data.tar.gz: c346972569d9e190445eceea9c9c7eef9bffa8d0dea95825ed53c1025e52e74a
5
5
  SHA512:
6
- metadata.gz: a289ee47e7d21efdf378737f101dca5efb11a0938ff41b0be804a39a2b0e2d6a694a78e57b0037d65a0e9fe136042d32f3d4377f5320bf9f63d0119d7bb51d33
7
- data.tar.gz: e836803b9664e2e5548af277978ed7170b8f18e79b767d908f2bbe8005464bc016fb6261bec5fa84d60774c632b6a42578e1d56899a63f90309c49941050750f
6
+ metadata.gz: a31b2bcd62a7157933af6e3e57a28b3314a9b42fd91c8250304f77a2eb8eb74c4df5651f1952ff76e237aa586eae77135e8d84dc24672e42eba9a95c2c42c899
7
+ data.tar.gz: '09479b26facc3ae8584d584cd1d2982c3aa46f0c43cffa5a0ec91baa93d036647ce5a910224f51f17ed26c157755e8f5eed3fbbf3d0994a849086258d269d866'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## Unreleased
2
+
3
+ ## [0.4.0] - 2026-01-25
4
+
5
+ - Replace http.rb with httpx for HTTP client, remove `Pool` class ([#3](https://github.com/kukicola/clickhouse-rb/pull/3))
6
+
7
+ ## [0.3.0] - 2025-12-31
8
+
9
+ - Make responses more user-friendly ([#1](https://github.com/kukicola/clickhouse-rb/pull/1))
10
+
1
11
  ## [0.2.0] - 2025-12-28
2
12
 
3
13
  - Added instrumentation support with configurable `instrumenter` (defaults to `NullInstrumenter`)
data/README.md CHANGED
@@ -5,8 +5,8 @@ Fast Ruby client for ClickHouse database using the Native binary format for effi
5
5
  ## Features
6
6
 
7
7
  - Native binary format parsing (faster than JSON/TSV)
8
- - Persistent HTTP connections
9
- - Connection pooling for thread-safe concurrent access
8
+ - Persistent HTTP connections with built-in connection pooling
9
+ - Thread-safe concurrent access
10
10
  - Supports all common ClickHouse data types
11
11
 
12
12
  ## Installation
@@ -51,51 +51,60 @@ end
51
51
 
52
52
  ```ruby
53
53
  conn = Clickhouse::Connection.new
54
- response = conn.query("SELECT * FROM users WHERE id = 1")
54
+ response = conn.query("SELECT id, name FROM users WHERE active = true")
55
55
 
56
- response.rows.each do |row|
57
- puts row.inspect
56
+ response.each do |row|
57
+ puts "#{row[:id]}: #{row[:name]}"
58
58
  end
59
59
  ```
60
60
 
61
- ### Connection Pool
61
+ ### Thread-Safe Usage
62
62
 
63
- For multi-threaded applications:
63
+ Connections use httpx's built-in connection pooling, making them safe for concurrent use:
64
64
 
65
65
  ```ruby
66
- pool = Clickhouse::Pool.new
66
+ conn = Clickhouse::Connection.new
67
67
 
68
- # Thread-safe queries
69
68
  threads = 10.times.map do
70
- Thread.new { pool.query("SELECT 1") }
69
+ Thread.new { conn.query("SELECT 1") }
71
70
  end
72
71
  threads.each(&:join)
73
72
  ```
74
73
 
75
- Pool size and timeout are configured globally:
74
+ Pool settings are configured globally:
76
75
 
77
76
  ```ruby
78
77
  Clickhouse.configure do |config|
79
- config.pool_size = 10
78
+ config.pool_size = 10
80
79
  config.pool_timeout = 5
81
80
  end
82
81
  ```
83
82
 
84
83
  ### Working with Results
85
84
 
85
+ Response objects implement `Enumerable`, allowing direct iteration:
86
+
86
87
  ```ruby
87
88
  response = conn.query("SELECT id, name, created_at FROM users")
88
89
 
90
+ # Iterate over rows as hashes with symbol keys
91
+ response.each { |row| puts row[:name] }
92
+
93
+ # Use any Enumerable method
94
+ response.map { |row| row[:id] }
95
+ response.select { |row| row[:id] > 10 }
96
+ response.first # => {id: 1, name: "Alice", created_at: 2024-01-01 00:00:00 UTC}
97
+
89
98
  # Access raw rows (arrays)
90
99
  response.rows # => [[1, "Alice", 2024-01-01 00:00:00 UTC], ...]
91
- response.columns # => ["id", "name", "created_at"]
92
- response.types # => ["UInt64", "String", "DateTime"]
100
+ response.columns # => [:id, :name, :created_at]
101
+ response.types # => [:UInt64, :String, :DateTime]
93
102
 
94
103
  # Convert to array of hashes
95
- response.to_a # => [{"id" => 1, "name" => "Alice", ...}, ...]
104
+ response.to_a # => [{id: 1, name: "Alice", ...}, ...]
96
105
 
97
- # Query summary from ClickHouse
98
- response.summary # => {"read_rows" => "1", "read_bytes" => "42", ...}
106
+ # Query summary from ClickHouse (symbol keys)
107
+ response.summary # => {read_rows: "1", read_bytes: "42", ...}
99
108
  ```
100
109
 
101
110
  ### Query Parameters
@@ -141,6 +150,8 @@ response = conn.query(
141
150
  | `username` | `""` | Authentication username |
142
151
  | `password` | `""` | Authentication password |
143
152
  | `connection_timeout` | `5` | Connection timeout in seconds |
153
+ | `read_timeout` | `60` | Read timeout in seconds |
154
+ | `write_timeout` | `60` | Write timeout in seconds |
144
155
  | `pool_size` | `100` | Connection pool size |
145
156
  | `pool_timeout` | `5` | Pool checkout timeout in seconds |
146
157
  | `instrumenter` | `NullInstrumenter` | Instrumenter for query instrumentation |
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clickhouse
4
+ # Wrapper for HTTP response body providing position tracking and EOF detection.
5
+ # @api private
6
+ class BodyReader
7
+ # Creates a new body reader.
8
+ #
9
+ # @param body [#read, #bytesize, #close] HTTP response body
10
+ def initialize(body)
11
+ @body = body
12
+ @pos = 0
13
+ @size = body.bytesize
14
+ end
15
+
16
+ # Closes the underlying body.
17
+ #
18
+ # @return [void]
19
+ def close
20
+ @body.close
21
+ end
22
+
23
+ # Returns true if at end of stream.
24
+ #
25
+ # @return [Boolean]
26
+ def eof?
27
+ @pos >= @size
28
+ end
29
+
30
+ # Reads exactly n bytes from the body.
31
+ #
32
+ # @param n [Integer] number of bytes to read
33
+ # @return [String] binary string of n bytes
34
+ def read(n)
35
+ result = @body.read(n)
36
+ @pos += n
37
+ result
38
+ end
39
+ end
40
+ end
@@ -20,6 +20,8 @@ module Clickhouse
20
20
  username: "",
21
21
  password: "",
22
22
  connection_timeout: 5,
23
+ read_timeout: 60,
24
+ write_timeout: 60,
23
25
  pool_size: 100,
24
26
  pool_timeout: 5,
25
27
  instrumenter: NullInstrumenter.new
@@ -32,10 +34,12 @@ module Clickhouse
32
34
  # @return [String] Username for authentication
33
35
  # @return [String] Password for authentication
34
36
  # @return [Integer] Connection timeout in seconds
37
+ # @return [Integer] Read timeout in seconds
38
+ # @return [Integer] Write timeout in seconds
35
39
  # @return [Integer] Connection pool size
36
40
  # @return [Integer] Pool checkout timeout in seconds
37
41
  # @return [#instrument] Instrumenter for query instrumentation
38
- attr_accessor :scheme, :host, :port, :database, :username, :password, :connection_timeout, :pool_size, :pool_timeout, :instrumenter
42
+ attr_accessor :scheme, :host, :port, :database, :username, :password, :connection_timeout, :read_timeout, :write_timeout, :pool_size, :pool_timeout, :instrumenter
39
43
 
40
44
  # Creates a new configuration instance.
41
45
  #
@@ -46,7 +50,9 @@ module Clickhouse
46
50
  # @option params [String] :database database name (default: "default")
47
51
  # @option params [String] :username authentication username (default: "")
48
52
  # @option params [String] :password authentication password (default: "")
49
- # @option params [Integer] :connection_timeout timeout in seconds (default: 5)
53
+ # @option params [Integer] :connection_timeout connection timeout in seconds (default: 5)
54
+ # @option params [Integer] :read_timeout read timeout in seconds (default: 60)
55
+ # @option params [Integer] :write_timeout write timeout in seconds (default: 60)
50
56
  # @option params [Integer] :pool_size connection pool size (default: 100)
51
57
  # @option params [Integer] :pool_timeout pool checkout timeout (default: 5)
52
58
  def initialize(params = {})
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http"
3
+ require "httpx"
4
4
  require "json"
5
5
 
6
6
  module Clickhouse
@@ -12,10 +12,19 @@ module Clickhouse
12
12
  # @param config [Config] configuration instance
13
13
  def initialize(config)
14
14
  @config = config
15
- @http_client = HTTP.persistent("#{config.scheme}://#{config.host}:#{config.port}")
16
- .use(:auto_deflate)
17
- .use(:auto_inflate)
18
- .timeout(connect: config.connection_timeout)
15
+ @base_url = "#{config.scheme}://#{config.host}:#{config.port}"
16
+ @http_client = HTTPX.plugin(:persistent, close_on_fork: true)
17
+ .with(
18
+ timeout: {
19
+ connect_timeout: config.connection_timeout,
20
+ read_timeout: config.read_timeout,
21
+ write_timeout: config.write_timeout
22
+ },
23
+ pool_options: {
24
+ max_connections_per_origin: config.pool_size,
25
+ pool_timeout: config.pool_timeout
26
+ }
27
+ )
19
28
 
20
29
  @default_headers = {
21
30
  "Accept-Encoding" => "gzip",
@@ -34,11 +43,13 @@ module Clickhouse
34
43
  # @raise [QueryError] if the query fails
35
44
  def execute(sql, options = {})
36
45
  query_params = {database: @config.database}.merge(options[:params] || {})
37
- response = @http_client.post("/", params: query_params, body: sql, headers: @default_headers)
46
+ response = @http_client.post(@base_url, params: query_params, body: sql, headers: @default_headers)
38
47
 
39
- summary = JSON.parse(response.headers["X-ClickHouse-Summary"])
48
+ raise QueryError, response.error.message if response.error
40
49
 
41
- raise QueryError, response.body.to_s unless response.status.success?
50
+ summary = JSON.parse(response.headers["x-clickhouse-summary"], symbolize_names: true)
51
+
52
+ raise QueryError, response.body.to_s unless response.status == 200
42
53
 
43
54
  TransportResult.new(body: response.body, summary: summary)
44
55
  end
@@ -11,9 +11,9 @@ module Clickhouse
11
11
 
12
12
  # Creates a new parser.
13
13
  #
14
- # @param body [#readpartial] response body to parse
14
+ # @param body [#read] response body to parse
15
15
  def initialize(body)
16
- @body = body
16
+ @reader = BodyReader.new(body)
17
17
  @columns = []
18
18
  @types = []
19
19
  @rows = []
@@ -24,11 +24,10 @@ module Clickhouse
24
24
  # @return [Response] parsed response with columns, types, and rows
25
25
  # @raise [UnsupportedTypeError] if an unsupported data type is encountered
26
26
  def parse
27
- @reader = BufferedReader.new(@body)
28
27
  parse_block until @reader.eof?
29
28
  Response.new(columns: @columns, types: @types, rows: @rows)
30
29
  ensure
31
- @reader.flush
30
+ @reader.close
32
31
  end
33
32
 
34
33
  private
@@ -46,8 +45,8 @@ module Clickhouse
46
45
  col_type = read_string
47
46
 
48
47
  if @columns.length < num_columns
49
- @columns << col_name
50
- @types << col_type
48
+ @columns << col_name.to_sym
49
+ @types << col_type.to_sym
51
50
  end
52
51
 
53
52
  columns_data << read_column(col_type, num_rows)
@@ -65,14 +64,14 @@ module Clickhouse
65
64
  when "UInt16" then read_uint16_column(num_rows)
66
65
  when "UInt32" then read_uint32_column(num_rows)
67
66
  when "UInt64" then read_uint64_column(num_rows)
68
- when "UInt128" then Array.new(num_rows) { read_uint128 }
69
- when "UInt256" then Array.new(num_rows) { read_uint256 }
67
+ when "UInt128" then read_uint128_column(num_rows)
68
+ when "UInt256" then read_uint256_column(num_rows)
70
69
  when "Int8" then read_int8_column(num_rows)
71
70
  when "Int16" then read_int16_column(num_rows)
72
71
  when "Int32" then read_int32_column(num_rows)
73
72
  when "Int64" then read_int64_column(num_rows)
74
- when "Int128" then Array.new(num_rows) { read_int128 }
75
- when "Int256" then Array.new(num_rows) { read_int256 }
73
+ when "Int128" then read_int128_column(num_rows)
74
+ when "Int256" then read_int256_column(num_rows)
76
75
 
77
76
  # Floats
78
77
  when "Float32" then read_float32_column(num_rows)
@@ -82,7 +81,7 @@ module Clickhouse
82
81
  when "Bool" then read_bool_column(num_rows)
83
82
 
84
83
  # Strings
85
- when "String" then Array.new(num_rows) { read_string }
84
+ when "String" then read_string_column(num_rows)
86
85
  when /^FixedString\((\d+)\)$/ then read_fixed_string_column($1.to_i, num_rows)
87
86
 
88
87
  # Dates and Times
@@ -92,11 +91,11 @@ module Clickhouse
92
91
  when /^DateTime64\((\d+)(?:,.*)?\)$/ then read_datetime64_column($1.to_i, num_rows)
93
92
 
94
93
  # UUID
95
- when "UUID" then Array.new(num_rows) { read_uuid }
94
+ when "UUID" then read_uuid_column(num_rows)
96
95
 
97
96
  # IP addresses
98
- when "IPv4" then Array.new(num_rows) { read_ipv4 }
99
- when "IPv6" then Array.new(num_rows) { read_ipv6 }
97
+ when "IPv4" then read_ipv4_column(num_rows)
98
+ when "IPv6" then read_ipv6_column(num_rows)
100
99
 
101
100
  # Decimals - ClickHouse always returns Decimal(precision, scale)
102
101
  when /^Decimal\((\d+),\s*(\d+)\)$/ then read_decimal_column($1.to_i, $2.to_i, num_rows)
@@ -145,6 +144,14 @@ module Clickhouse
145
144
  @reader.read(num_rows * 8).unpack("Q<*")
146
145
  end
147
146
 
147
+ def read_uint128_column(num_rows)
148
+ Array.new(num_rows) { read_le_bytes(16) }
149
+ end
150
+
151
+ def read_uint256_column(num_rows)
152
+ Array.new(num_rows) { read_le_bytes(32) }
153
+ end
154
+
148
155
  def read_int8_column(num_rows)
149
156
  @reader.read(num_rows).unpack("c*")
150
157
  end
@@ -161,6 +168,14 @@ module Clickhouse
161
168
  @reader.read(num_rows * 8).unpack("q<*")
162
169
  end
163
170
 
171
+ def read_int128_column(num_rows)
172
+ Array.new(num_rows) { read_signed_le_bytes(16) }
173
+ end
174
+
175
+ def read_int256_column(num_rows)
176
+ Array.new(num_rows) { read_signed_le_bytes(32) }
177
+ end
178
+
164
179
  def read_float32_column(num_rows)
165
180
  @reader.read(num_rows * 4).unpack("e*")
166
181
  end
@@ -173,6 +188,10 @@ module Clickhouse
173
188
  @reader.read(num_rows).bytes.map { |b| b == 1 }
174
189
  end
175
190
 
191
+ def read_string_column(num_rows)
192
+ Array.new(num_rows) { read_string }
193
+ end
194
+
176
195
  def read_fixed_string_column(length, num_rows)
177
196
  Array.new(num_rows) { @reader.read(length).force_encoding(Encoding::UTF_8) }
178
197
  end
@@ -197,6 +216,18 @@ module Clickhouse
197
216
  end
198
217
  end
199
218
 
219
+ def read_uuid_column(num_rows)
220
+ Array.new(num_rows) { read_uuid }
221
+ end
222
+
223
+ def read_ipv4_column(num_rows)
224
+ Array.new(num_rows) { read_ipv4 }
225
+ end
226
+
227
+ def read_ipv6_column(num_rows)
228
+ Array.new(num_rows) { read_ipv6 }
229
+ end
230
+
200
231
  def read_decimal_column(precision, scale, num_rows)
201
232
  divisor = 10**scale
202
233
  if precision <= 9
@@ -216,8 +247,9 @@ module Clickhouse
216
247
  result = 0
217
248
  shift = 0
218
249
  loop do
219
- byte = @reader.read_byte
220
- return result if byte.nil?
250
+ byte_str = @reader.read(1)
251
+ return result if byte_str.nil? || byte_str.empty?
252
+ byte = byte_str.ord
221
253
  result |= (byte & 0x7F) << shift
222
254
  break if (byte & 0x80) == 0
223
255
  shift += 7
@@ -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
@@ -5,20 +5,27 @@ module Clickhouse
5
5
  #
6
6
  # @example
7
7
  # response = conn.query("SELECT id, name FROM users")
8
- # response.columns # => ["id", "name"]
8
+ # response.columns # => [:id, :name]
9
9
  # response.rows # => [[1, "Alice"], [2, "Bob"]]
10
- # response.to_a # => [{"id" => 1, "name" => "Alice"}, ...]
10
+ # response.each { |row| puts row[:name] }
11
11
  Response = Data.define(:columns, :types, :rows, :summary) do
12
- # @param columns [Array<String>] column names
13
- # @param types [Array<String>] column types
12
+ include Enumerable
13
+
14
+ # @param columns [Array<Symbol>] column names
15
+ # @param types [Array<Symbol>] column types
14
16
  # @param rows [Array<Array>] row data
15
- # @param summary [Hash, nil] ClickHouse query summary
17
+ # @param summary [Hash, nil] ClickHouse query summary with symbol keys
16
18
  def initialize(columns: [], types: [], rows: [], summary: nil)
17
19
  super
18
20
  end
19
21
 
20
- # Converts rows to an array of hashes.
21
- # @return [Array<Hash>] rows as hashes with column names as keys
22
- def to_a = rows.map { |row| columns.zip(row).to_h }
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?
27
+
28
+ rows.each { |row| yield columns.zip(row).to_h }
29
+ end
23
30
  end
24
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clickhouse
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/clickhouse.rb CHANGED
@@ -6,9 +6,8 @@ require_relative "clickhouse/config"
6
6
  require_relative "clickhouse/transport_result"
7
7
  require_relative "clickhouse/http_transport"
8
8
  require_relative "clickhouse/connection"
9
- require_relative "clickhouse/pool"
10
9
  require_relative "clickhouse/response"
11
- require_relative "clickhouse/buffered_reader"
10
+ require_relative "clickhouse/body_reader"
12
11
  require_relative "clickhouse/native_format_parser"
13
12
 
14
13
  # Ruby client for ClickHouse database with Native format support.
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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Bąk
@@ -24,33 +24,19 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '3.1'
26
26
  - !ruby/object:Gem::Dependency
27
- name: connection_pool
27
+ name: httpx
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '3.0'
32
+ version: '1.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '3.0'
40
- - !ruby/object:Gem::Dependency
41
- name: http
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '5.0'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '5.0'
39
+ version: '1.0'
54
40
  description: Fast Ruby client for ClickHouse database using the Native binary format
55
41
  for efficient data transfer
56
42
  email:
@@ -65,13 +51,12 @@ files:
65
51
  - README.md
66
52
  - lib/clickhouse-rb.rb
67
53
  - lib/clickhouse.rb
68
- - lib/clickhouse/buffered_reader.rb
54
+ - lib/clickhouse/body_reader.rb
69
55
  - lib/clickhouse/config.rb
70
56
  - lib/clickhouse/connection.rb
71
57
  - lib/clickhouse/http_transport.rb
72
58
  - lib/clickhouse/native_format_parser.rb
73
59
  - lib/clickhouse/null_instrumenter.rb
74
- - lib/clickhouse/pool.rb
75
60
  - lib/clickhouse/response.rb
76
61
  - lib/clickhouse/transport_result.rb
77
62
  - lib/clickhouse/version.rb
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Clickhouse
4
- # Buffered binary reader for streaming IO.
5
- # @api private
6
- class BufferedReader
7
- # Creates a new buffered reader.
8
- #
9
- # @param io [#readpartial] IO object supporting readpartial
10
- def initialize(io)
11
- @io = io
12
- @buffer = String.new(encoding: Encoding::BINARY)
13
- @eof = false
14
- end
15
-
16
- # Returns true if at end of stream.
17
- #
18
- # @return [Boolean]
19
- def eof?
20
- fill if @buffer.empty?
21
- @buffer.empty?
22
- end
23
-
24
- # Reads exactly n bytes from the stream.
25
- #
26
- # @param n [Integer] number of bytes to read
27
- # @return [String] binary string of n bytes
28
- def read(n)
29
- fill until @buffer.bytesize >= n
30
- @buffer.slice!(0, n)
31
- end
32
-
33
- # Reads a single byte from the stream.
34
- #
35
- # @return [Integer, nil] byte value (0-255) or nil if at EOF
36
- def read_byte
37
- fill if @buffer.empty?
38
- return if @buffer.empty?
39
-
40
- @buffer.slice!(0, 1).ord
41
- end
42
-
43
- # Drains remaining data from the stream.
44
- #
45
- # @return [void]
46
- def flush
47
- @buffer.clear
48
- nil while @io.readpartial
49
- end
50
-
51
- private
52
-
53
- def fill
54
- return if @eof
55
-
56
- loop do
57
- chunk = @io.readpartial
58
- if chunk.nil?
59
- @eof = true
60
- break
61
- elsif !chunk.empty?
62
- @buffer << chunk
63
- break
64
- end
65
- # Empty chunk - keep reading (gzip header processing)
66
- end
67
- end
68
- end
69
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "connection_pool"
4
-
5
- module Clickhouse
6
- # Thread-safe connection pool for ClickHouse.
7
- #
8
- # @example
9
- # pool = Clickhouse::Pool.new
10
- # response = pool.query("SELECT * FROM users")
11
- #
12
- # @example Concurrent usage
13
- # pool = Clickhouse::Pool.new
14
- # threads = 10.times.map do
15
- # Thread.new { pool.query("SELECT 1") }
16
- # end
17
- # threads.each(&:join)
18
- class Pool
19
- # Creates a new connection pool.
20
- #
21
- # @param config [Config] configuration instance (defaults to global config)
22
- # Pool size and timeout are read from config.pool_size and config.pool_timeout
23
- def initialize(config = Clickhouse.config)
24
- @pool = ConnectionPool.new(size: config.pool_size, timeout: config.pool_timeout) do
25
- Connection.new(config)
26
- end
27
- end
28
-
29
- # Executes a SQL query using a pooled connection.
30
- #
31
- # @param sql [String] SQL query to execute
32
- # @param options [Hash] query options
33
- # @option options [Hash] :params query parameters
34
- # @return [Response] query response with rows, columns, and metadata
35
- def query(sql, options = {})
36
- @pool.with { |conn| conn.query(sql, options) }
37
- end
38
- end
39
- end