clickhouse-rb 0.3.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: b3665b6f44472615341970c7b6169eaa3e54e7df0132ad866d24b8e24cb170df
4
- data.tar.gz: b5948f2b40a4e969ed35daf92dfb24f3da23215fa113b86b168c3ab911f20335
3
+ metadata.gz: 86a862148595c1c9794a3d122e750db7959f5d4478b9dc3f65ee0f00319c55c0
4
+ data.tar.gz: c346972569d9e190445eceea9c9c7eef9bffa8d0dea95825ed53c1025e52e74a
5
5
  SHA512:
6
- metadata.gz: 777e4c17a2d3278fb7a1221ed16f5d4ea01f16eeaac31c5e24ba52f81a2b5dc81613ea379c1e722b9d75f55c3850f6761a1ad5e45265e3be4a2199449777f68c
7
- data.tar.gz: af8ac7a565c217142e5234d4d83dc9caf70cf2aca86714985b76155ecf8434076f27b9716794f09090b392033a49a7b14097645bc8b180190d089df4e45616c5
6
+ metadata.gz: a31b2bcd62a7157933af6e3e57a28b3314a9b42fd91c8250304f77a2eb8eb74c4df5651f1952ff76e237aa586eae77135e8d84dc24672e42eba9a95c2c42c899
7
+ data.tar.gz: '09479b26facc3ae8584d584cd1d2982c3aa46f0c43cffa5a0ec91baa93d036647ce5a910224f51f17ed26c157755e8f5eed3fbbf3d0994a849086258d269d866'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## Unreleased
2
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
+
3
7
  ## [0.3.0] - 2025-12-31
4
8
 
5
9
  - Make responses more user-friendly ([#1](https://github.com/kukicola/clickhouse-rb/pull/1))
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
@@ -58,25 +58,24 @@ response.each do |row|
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
  ```
@@ -151,6 +150,8 @@ response = conn.query(
151
150
  | `username` | `""` | Authentication username |
152
151
  | `password` | `""` | Authentication password |
153
152
  | `connection_timeout` | `5` | Connection timeout in seconds |
153
+ | `read_timeout` | `60` | Read timeout in seconds |
154
+ | `write_timeout` | `60` | Write timeout in seconds |
154
155
  | `pool_size` | `100` | Connection pool size |
155
156
  | `pool_timeout` | `5` | Pool checkout timeout in seconds |
156
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"], symbolize_names: true)
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
@@ -248,8 +247,9 @@ module Clickhouse
248
247
  result = 0
249
248
  shift = 0
250
249
  loop do
251
- byte = @reader.read_byte
252
- 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
253
253
  result |= (byte & 0x7F) << shift
254
254
  break if (byte & 0x80) == 0
255
255
  shift += 7
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clickhouse
4
- VERSION = "0.3.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.3.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