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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +10 -9
- data/lib/clickhouse/body_reader.rb +40 -0
- data/lib/clickhouse/config.rb +8 -2
- data/lib/clickhouse/http_transport.rb +19 -8
- data/lib/clickhouse/native_format_parser.rb +6 -6
- data/lib/clickhouse/version.rb +1 -1
- data/lib/clickhouse.rb +1 -2
- metadata +5 -20
- data/lib/clickhouse/buffered_reader.rb +0 -69
- data/lib/clickhouse/pool.rb +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86a862148595c1c9794a3d122e750db7959f5d4478b9dc3f65ee0f00319c55c0
|
|
4
|
+
data.tar.gz: c346972569d9e190445eceea9c9c7eef9bffa8d0dea95825ed53c1025e52e74a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
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
|
-
###
|
|
61
|
+
### Thread-Safe Usage
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Connections use httpx's built-in connection pooling, making them safe for concurrent use:
|
|
64
64
|
|
|
65
65
|
```ruby
|
|
66
|
-
|
|
66
|
+
conn = Clickhouse::Connection.new
|
|
67
67
|
|
|
68
|
-
# Thread-safe queries
|
|
69
68
|
threads = 10.times.map do
|
|
70
|
-
Thread.new {
|
|
69
|
+
Thread.new { conn.query("SELECT 1") }
|
|
71
70
|
end
|
|
72
71
|
threads.each(&:join)
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
Pool
|
|
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
|
data/lib/clickhouse/config.rb
CHANGED
|
@@ -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 "
|
|
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
|
-
@
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
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(
|
|
46
|
+
response = @http_client.post(@base_url, params: query_params, body: sql, headers: @default_headers)
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
raise QueryError, response.error.message if response.error
|
|
40
49
|
|
|
41
|
-
|
|
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 [#
|
|
14
|
+
# @param body [#read] response body to parse
|
|
15
15
|
def initialize(body)
|
|
16
|
-
@
|
|
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.
|
|
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
|
-
|
|
252
|
-
return result if
|
|
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
|
data/lib/clickhouse/version.rb
CHANGED
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/
|
|
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.
|
|
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:
|
|
27
|
+
name: httpx
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
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: '
|
|
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/
|
|
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
|
data/lib/clickhouse/pool.rb
DELETED
|
@@ -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
|