click_house 1.4.0 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +4 -28
- data/.gitignore +1 -0
- data/.rubocop.yml +28 -4
- data/CHANGELOG.md +13 -1
- data/Gemfile.lock +52 -34
- data/README.md +38 -35
- data/click_house.gemspec +2 -4
- data/docker-compose.yml +1 -1
- data/lib/click_house/config.rb +8 -2
- data/lib/click_house/connection.rb +25 -8
- data/lib/click_house/extend/connection_explaining.rb +4 -2
- data/lib/click_house/extend/connection_healthy.rb +1 -2
- data/lib/click_house/extend/connection_selective.rb +3 -3
- data/lib/click_house/middleware/logging.rb +24 -5
- data/lib/click_house/response/factory.rb +6 -1
- data/lib/click_house/response/result_set.rb +6 -2
- data/lib/click_house/type/float_type.rb +1 -1
- data/lib/click_house/type/integer_type.rb +1 -1
- data/lib/click_house/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85fbf603cf09ee0a076a3ca0fa0add17c167919c458008a5616a4a7b064b0790
|
4
|
+
data.tar.gz: 95cf0f595c70a2943bba62a0d8568b4f432b3fdf27365b036377a66960733efe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a31451c3ed68aa5ff213cabaa51578723212fe43d0e63a1b58a1d68b135facece27f2d52025512d41862ba7495dcd05972f3a9cd8a4d01cc25916e2e502f42b
|
7
|
+
data.tar.gz: 0d78cbad863fcb89e8d58da4c10eeec60013877c5097480c0e6bc190c9698641f5ac95c116bd3b938f7ce927aa58ad10abae73729ce579149d40a51e529320c7
|
data/.github/workflows/main.yml
CHANGED
@@ -8,13 +8,13 @@ jobs:
|
|
8
8
|
|
9
9
|
services:
|
10
10
|
clickhouse:
|
11
|
-
image: yandex/clickhouse-server:
|
11
|
+
image: yandex/clickhouse-server:22.1
|
12
12
|
ports:
|
13
13
|
- 8123:8123
|
14
14
|
|
15
15
|
strategy:
|
16
16
|
matrix:
|
17
|
-
ruby-version: [3.0, 2.7, 2.6
|
17
|
+
ruby-version: [3.1, '3.0', 2.7, 2.6]
|
18
18
|
|
19
19
|
steps:
|
20
20
|
- uses: actions/checkout@v2
|
@@ -23,19 +23,7 @@ jobs:
|
|
23
23
|
uses: ruby/setup-ruby@v1
|
24
24
|
with:
|
25
25
|
ruby-version: ${{ matrix.ruby-version }}
|
26
|
-
|
27
|
-
- name: Cache gems
|
28
|
-
uses: actions/cache@v1
|
29
|
-
with:
|
30
|
-
path: vendor/bundle
|
31
|
-
key: ${{ runner.os }}-${{ matrix.ruby-version }}-bundler-${{ hashFiles('**/Gemfile.lock') }}
|
32
|
-
restore-keys: |
|
33
|
-
${{ runner.os }}-${{ matrix.ruby-version }}-bundler-
|
34
|
-
|
35
|
-
- name: Install gems
|
36
|
-
run: |
|
37
|
-
bundle config path vendor/bundle
|
38
|
-
bundle install --jobs 4 --retry 3
|
26
|
+
bundler-cache: true # 'bundle install' and cache
|
39
27
|
|
40
28
|
- name: Run tests
|
41
29
|
run: bundle exec rspec
|
@@ -50,19 +38,7 @@ jobs:
|
|
50
38
|
uses: ruby/setup-ruby@v1
|
51
39
|
with:
|
52
40
|
ruby-version: 2.7
|
53
|
-
|
54
|
-
- name: Cache gems
|
55
|
-
uses: actions/cache@v1
|
56
|
-
with:
|
57
|
-
path: vendor/bundle
|
58
|
-
key: ${{ runner.os }}-bundler-${{ hashFiles('**/Gemfile.lock') }}
|
59
|
-
restore-keys: |
|
60
|
-
${{ runner.os }}-bundler-
|
61
|
-
|
62
|
-
- name: Install gems
|
63
|
-
run: |
|
64
|
-
bundle config path vendor/bundle
|
65
|
-
bundle install --jobs 4 --retry 3
|
41
|
+
bundler-cache: true # 'bundle install' and cache
|
66
42
|
|
67
43
|
- name: Run Rubocop
|
68
44
|
run: bundle exec rubocop
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -13,9 +13,9 @@ AllCops:
|
|
13
13
|
Bundler/OrderedGems:
|
14
14
|
Enabled: false
|
15
15
|
|
16
|
-
# ==============================
|
17
|
-
|
18
|
-
Enabled:
|
16
|
+
# ============================== Gemspec ======================
|
17
|
+
Gemspec/DateAssignment:
|
18
|
+
Enabled: true
|
19
19
|
|
20
20
|
# =============================== Performance =======================
|
21
21
|
Performance/AncestorsInclude:
|
@@ -46,6 +46,12 @@ Performance/ConstantRegexp:
|
|
46
46
|
Enabled: true
|
47
47
|
Performance/MethodObjectAsBlock:
|
48
48
|
Enabled: false
|
49
|
+
Performance/RedundantEqualityComparisonBlock:
|
50
|
+
Enabled: true
|
51
|
+
Performance/RedundantSplitRegexpArgument:
|
52
|
+
Enabled: true
|
53
|
+
Performance/MapCompact:
|
54
|
+
Enabled: true
|
49
55
|
|
50
56
|
# ============================== Metrics ============================
|
51
57
|
Metrics/ClassLength:
|
@@ -69,6 +75,8 @@ Naming/MethodParameterName:
|
|
69
75
|
Enabled: false
|
70
76
|
Naming/AccessorMethodName:
|
71
77
|
Enabled: false
|
78
|
+
Naming/InclusiveLanguage:
|
79
|
+
Enabled: true
|
72
80
|
|
73
81
|
# ============================== Layout =============================
|
74
82
|
Layout/LineLength:
|
@@ -107,7 +115,7 @@ Layout/SpaceAroundMethodCallOperator:
|
|
107
115
|
Enabled: true
|
108
116
|
Layout/SpaceBeforeBrackets:
|
109
117
|
Enabled: true
|
110
|
-
|
118
|
+
Layout/LineEndStringConcatenationIndentation:
|
111
119
|
Enabled: true
|
112
120
|
|
113
121
|
# ============================== Style ==============================
|
@@ -227,6 +235,18 @@ Style/EndlessMethod:
|
|
227
235
|
Enabled: true
|
228
236
|
Style/IfWithBooleanLiteralBranches:
|
229
237
|
Enabled: true
|
238
|
+
Style/HashConversion:
|
239
|
+
Enabled: true
|
240
|
+
Style/Documentation:
|
241
|
+
Enabled: false
|
242
|
+
Style/InPatternThen:
|
243
|
+
Enabled: true
|
244
|
+
Style/MultilineInPatternThen:
|
245
|
+
Enabled: true
|
246
|
+
Style/QuotedSymbols:
|
247
|
+
Enabled: true
|
248
|
+
Style/StringChars:
|
249
|
+
Enabled: true
|
230
250
|
|
231
251
|
# ============================== Lint ==============================
|
232
252
|
Lint/DuplicateMethods:
|
@@ -311,3 +331,7 @@ Lint/SymbolConversion:
|
|
311
331
|
Enabled: true
|
312
332
|
Lint/TripleQuotes:
|
313
333
|
Enabled: true
|
334
|
+
Lint/AmbiguousAssignment:
|
335
|
+
Enabled: true
|
336
|
+
Lint/EmptyInPattern:
|
337
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
# 1.6.1
|
2
|
+
* [PR](https://github.com/shlima/click_house/pull/26) call logging middleware when an error is raised
|
3
|
+
|
4
|
+
# 1.6.0
|
5
|
+
* [PR](https://github.com/shlima/click_house/pull/19) handle value returned as nil in float and integer types (case of Aggregate Function Combinators)
|
6
|
+
* [PR](https://github.com/shlima/click_house/pull/18) Fix Faraday deprecation
|
7
|
+
|
8
|
+
# 1.5.0
|
9
|
+
* add support for 'WITH TOTALS' modifier in response
|
10
|
+
* send SQL in GET request's body [#12](https://github.com/shlima/click_house/pull/12)
|
11
|
+
* add support of 'WITH TOTALS' on a resulting set
|
12
|
+
|
1
13
|
# 1.4.0
|
2
|
-
*
|
14
|
+
* fix decimal type casting [#11](https://github.com/shlima/click_house/issues/11)
|
3
15
|
|
4
16
|
# 1.3.9
|
5
17
|
* add `ClickHouse.connection.add_index`, `ClickHouse.connection.drop_index`
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
click_house (1.
|
5
|
-
faraday
|
4
|
+
click_house (1.6.1)
|
5
|
+
faraday (>= 1.7)
|
6
6
|
faraday_middleware
|
7
7
|
|
8
8
|
GEM
|
@@ -10,56 +10,74 @@ GEM
|
|
10
10
|
specs:
|
11
11
|
ast (2.4.2)
|
12
12
|
coderay (1.1.3)
|
13
|
-
diff-lcs (1.
|
14
|
-
faraday (1.
|
13
|
+
diff-lcs (1.5.0)
|
14
|
+
faraday (1.10.0)
|
15
|
+
faraday-em_http (~> 1.0)
|
16
|
+
faraday-em_synchrony (~> 1.0)
|
17
|
+
faraday-excon (~> 1.1)
|
18
|
+
faraday-httpclient (~> 1.0)
|
19
|
+
faraday-multipart (~> 1.0)
|
15
20
|
faraday-net_http (~> 1.0)
|
21
|
+
faraday-net_http_persistent (~> 1.0)
|
22
|
+
faraday-patron (~> 1.0)
|
23
|
+
faraday-rack (~> 1.0)
|
24
|
+
faraday-retry (~> 1.0)
|
25
|
+
ruby2_keywords (>= 0.0.4)
|
26
|
+
faraday-em_http (1.0.0)
|
27
|
+
faraday-em_synchrony (1.0.0)
|
28
|
+
faraday-excon (1.1.0)
|
29
|
+
faraday-httpclient (1.0.1)
|
30
|
+
faraday-multipart (1.0.3)
|
16
31
|
multipart-post (>= 1.2, < 3)
|
17
|
-
ruby2_keywords
|
18
32
|
faraday-net_http (1.0.1)
|
19
|
-
|
33
|
+
faraday-net_http_persistent (1.2.0)
|
34
|
+
faraday-patron (1.0.0)
|
35
|
+
faraday-rack (1.0.0)
|
36
|
+
faraday-retry (1.0.3)
|
37
|
+
faraday_middleware (1.2.0)
|
20
38
|
faraday (~> 1.0)
|
21
39
|
method_source (1.0.0)
|
22
40
|
multipart-post (2.1.1)
|
23
|
-
parallel (1.
|
24
|
-
parser (3.
|
41
|
+
parallel (1.22.1)
|
42
|
+
parser (3.1.1.0)
|
25
43
|
ast (~> 2.4.1)
|
26
|
-
pry (0.14.
|
44
|
+
pry (0.14.1)
|
27
45
|
coderay (~> 1.1)
|
28
46
|
method_source (~> 1.0)
|
29
|
-
rainbow (3.
|
30
|
-
rake (13.0.
|
31
|
-
regexp_parser (2.
|
32
|
-
rexml (3.2.
|
33
|
-
rspec (3.
|
34
|
-
rspec-core (~> 3.
|
35
|
-
rspec-expectations (~> 3.
|
36
|
-
rspec-mocks (~> 3.
|
37
|
-
rspec-core (3.
|
38
|
-
rspec-support (~> 3.
|
39
|
-
rspec-expectations (3.
|
47
|
+
rainbow (3.1.1)
|
48
|
+
rake (13.0.6)
|
49
|
+
regexp_parser (2.2.1)
|
50
|
+
rexml (3.2.5)
|
51
|
+
rspec (3.11.0)
|
52
|
+
rspec-core (~> 3.11.0)
|
53
|
+
rspec-expectations (~> 3.11.0)
|
54
|
+
rspec-mocks (~> 3.11.0)
|
55
|
+
rspec-core (3.11.0)
|
56
|
+
rspec-support (~> 3.11.0)
|
57
|
+
rspec-expectations (3.11.0)
|
40
58
|
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
-
rspec-support (~> 3.
|
42
|
-
rspec-mocks (3.
|
59
|
+
rspec-support (~> 3.11.0)
|
60
|
+
rspec-mocks (3.11.1)
|
43
61
|
diff-lcs (>= 1.2.0, < 2.0)
|
44
|
-
rspec-support (~> 3.
|
45
|
-
rspec-support (3.
|
46
|
-
rubocop (1.
|
62
|
+
rspec-support (~> 3.11.0)
|
63
|
+
rspec-support (3.11.0)
|
64
|
+
rubocop (1.26.1)
|
47
65
|
parallel (~> 1.10)
|
48
|
-
parser (>= 3.
|
66
|
+
parser (>= 3.1.0.0)
|
49
67
|
rainbow (>= 2.2.2, < 4.0)
|
50
68
|
regexp_parser (>= 1.8, < 3.0)
|
51
69
|
rexml
|
52
|
-
rubocop-ast (>= 1.
|
70
|
+
rubocop-ast (>= 1.16.0, < 2.0)
|
53
71
|
ruby-progressbar (~> 1.7)
|
54
72
|
unicode-display_width (>= 1.4.0, < 3.0)
|
55
|
-
rubocop-ast (1.
|
56
|
-
parser (>=
|
57
|
-
rubocop-performance (1.
|
58
|
-
rubocop (>=
|
73
|
+
rubocop-ast (1.16.0)
|
74
|
+
parser (>= 3.1.1.0)
|
75
|
+
rubocop-performance (1.13.3)
|
76
|
+
rubocop (>= 1.7.0, < 2.0)
|
59
77
|
rubocop-ast (>= 0.4.0)
|
60
78
|
ruby-progressbar (1.11.0)
|
61
|
-
ruby2_keywords (0.0.
|
62
|
-
unicode-display_width (2.
|
79
|
+
ruby2_keywords (0.0.5)
|
80
|
+
unicode-display_width (2.1.0)
|
63
81
|
|
64
82
|
PLATFORMS
|
65
83
|
ruby
|
@@ -74,4 +92,4 @@ DEPENDENCIES
|
|
74
92
|
rubocop-performance
|
75
93
|
|
76
94
|
BUNDLED WITH
|
77
|
-
2.
|
95
|
+
2.3.3
|
data/README.md
CHANGED
@@ -10,8 +10,8 @@
|
|
10
10
|
gem install click_house
|
11
11
|
```
|
12
12
|
|
13
|
-
A modern Ruby database driver for ClickHouse. [ClickHouse](https://clickhouse.yandex)
|
14
|
-
is a high-performance column-oriented database management system developed by
|
13
|
+
A modern Ruby database driver for ClickHouse. [ClickHouse](https://clickhouse.yandex)
|
14
|
+
is a high-performance column-oriented database management system developed by
|
15
15
|
[Yandex](https://yandex.com/company) which operates Russia's most popular search engine.
|
16
16
|
|
17
17
|
> This development was inspired by currently [unmaintainable alternative](https://github.com/archan937/clickhouse)
|
@@ -24,7 +24,7 @@ Well, the developers of ClickHouse themselves [discourage](https://github.com/ya
|
|
24
24
|
> TCP transport is more specific, we don't want to expose details.
|
25
25
|
Despite we have full compatibility of protocol of different versions of client and server, we want to keep the ability to "break" it for very old clients. And that protocol is not too clean to make a specification.
|
26
26
|
|
27
|
-
Yandex uses HTTP interface for working from Java and Perl, Python and Go as well as shell scripts.
|
27
|
+
Yandex uses HTTP interface for working from Java and Perl, Python and Go as well as shell scripts.
|
28
28
|
|
29
29
|
# TOC
|
30
30
|
|
@@ -53,15 +53,18 @@ ClickHouse.config do |config|
|
|
53
53
|
config.open_timeout = 3
|
54
54
|
config.ssl_verify = false
|
55
55
|
config.headers = {}
|
56
|
-
|
56
|
+
|
57
57
|
# or provide connection options separately
|
58
|
-
config.scheme = 'http'
|
59
|
-
config.host = 'localhost'
|
60
|
-
config.port = 'port'
|
61
|
-
|
58
|
+
config.scheme = 'http'
|
59
|
+
config.host = 'localhost'
|
60
|
+
config.port = 'port'
|
61
|
+
|
62
62
|
# if you use HTTP basic Auth
|
63
|
-
config.username = 'user'
|
64
|
-
config.password = 'password'
|
63
|
+
config.username = 'user'
|
64
|
+
config.password = 'password'
|
65
|
+
|
66
|
+
# if you want to add settings to all queries
|
67
|
+
config.global_params = { mutations_sync: 1 }
|
65
68
|
end
|
66
69
|
```
|
67
70
|
|
@@ -76,7 +79,7 @@ Now you are able to communicate with ClickHouse:
|
|
76
79
|
```ruby
|
77
80
|
ClickHouse.connection.ping #=> true
|
78
81
|
```
|
79
|
-
You can easily build a new raw connection and override any configuration parameter
|
82
|
+
You can easily build a new raw connection and override any configuration parameter
|
80
83
|
(such as database name, connection address)
|
81
84
|
|
82
85
|
```ruby
|
@@ -126,8 +129,8 @@ Select all type-casted result set
|
|
126
129
|
@result.to_a #=> [{"date"=>#<Date: 2000-01-01>, "id"=>1}]
|
127
130
|
|
128
131
|
# you can access raw data
|
129
|
-
@result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
|
130
|
-
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
|
132
|
+
@result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
|
133
|
+
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
|
131
134
|
@result.statistics #=> {"elapsed"=>0.0002271, "rows_read"=>2, "bytes_read"=>12}
|
132
135
|
```
|
133
136
|
|
@@ -182,7 +185,7 @@ response = ClickHouse.connection.execute <<~SQL
|
|
182
185
|
SELECT count(*) AS counter FROM rspec FORMAT RowBinary
|
183
186
|
SQL
|
184
187
|
|
185
|
-
response.body #=> "\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
188
|
+
response.body #=> "\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
186
189
|
```
|
187
190
|
|
188
191
|
## Insert
|
@@ -193,7 +196,7 @@ When column names and values are transferred separately
|
|
193
196
|
ClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|
|
194
197
|
buffer << [1, 'Mercury']
|
195
198
|
buffer << [2, 'Venus']
|
196
|
-
end
|
199
|
+
end
|
197
200
|
|
198
201
|
ClickHouse.connection.insert('table', columns: %i[id name], values: [[1, 'Mercury'], [2, 'Venus']])
|
199
202
|
#=> true
|
@@ -272,7 +275,7 @@ end
|
|
272
275
|
### Set table options
|
273
276
|
|
274
277
|
```ruby
|
275
|
-
ClickHouse.connection.create_table('visits',
|
278
|
+
ClickHouse.connection.create_table('visits',
|
276
279
|
order: 'year',
|
277
280
|
ttl: 'date + INTERVAL 1 DAY',
|
278
281
|
sample: 'year',
|
@@ -308,24 +311,24 @@ ClickHouse.connection.modify_column('table', 'column_name', type: :UInt64, defau
|
|
308
311
|
ClickHouse.connection.alter_table('table', 'DROP COLUMN user_id', cluster: nil)
|
309
312
|
|
310
313
|
# By SQL in a block
|
311
|
-
ClickHouse.connection.alter_table('table', cluster: nil) do
|
314
|
+
ClickHouse.connection.alter_table('table', cluster: nil) do
|
312
315
|
<<~SQL
|
313
316
|
MOVE PART '20190301_14343_16206_438' TO VOLUME 'slow'
|
314
|
-
SQL
|
317
|
+
SQL
|
315
318
|
end
|
316
319
|
```
|
317
320
|
|
318
321
|
## Type casting
|
319
322
|
|
320
323
|
By default gem provides all necessary type casting, but you may overwrite or define
|
321
|
-
your own logic
|
324
|
+
your own logic
|
322
325
|
|
323
326
|
```ruby
|
324
327
|
class DateType
|
325
328
|
def cast(value)
|
326
329
|
Date.parse(value)
|
327
|
-
end
|
328
|
-
|
330
|
+
end
|
331
|
+
|
329
332
|
def serialize(value)
|
330
333
|
value.strftime('%Y-%m-%d')
|
331
334
|
end
|
@@ -338,7 +341,7 @@ Actually `serialize` function is not used for now, but you may use it manually:
|
|
338
341
|
|
339
342
|
```ruby
|
340
343
|
time_type = ClickHouse::Type::DateTimeType.new
|
341
|
-
string_type = ClickHouse::Type::FixedStringType.new
|
344
|
+
string_type = ClickHouse::Type::FixedStringType.new
|
342
345
|
|
343
346
|
ClickHouse.connection.insert('table', columns: %i[name time]) do |buffer|
|
344
347
|
buffer << [string_type.serialize('a' * 1000, 20), time_type.serialize(Time.current, 'Europe/Moscow')]
|
@@ -353,20 +356,20 @@ data = @records.map do |record|
|
|
353
356
|
end
|
354
357
|
```
|
355
358
|
|
356
|
-
If native type supports arguments, define *String* type with `%s`
|
359
|
+
If native type supports arguments, define *String* type with `%s`
|
357
360
|
argument and *Numeric* type with `%d` argument:
|
358
361
|
|
359
362
|
```ruby
|
360
363
|
class DateTimeType
|
361
364
|
def cast(value, time_zone)
|
362
365
|
Time.parse("#{value} #{time_zone}")
|
363
|
-
end
|
366
|
+
end
|
364
367
|
end
|
365
368
|
|
366
369
|
ClickHouse.add_type('DateTime(%s)', DateTimeType.new)
|
367
370
|
```
|
368
371
|
|
369
|
-
if you need to redefine all built-in types with your implementation,
|
372
|
+
if you need to redefine all built-in types with your implementation,
|
370
373
|
just clear the default type system:
|
371
374
|
|
372
375
|
```ruby
|
@@ -406,7 +409,7 @@ development:
|
|
406
409
|
test:
|
407
410
|
database: ecliptic_test
|
408
411
|
<<: *default
|
409
|
-
|
412
|
+
|
410
413
|
production:
|
411
414
|
<<: *default
|
412
415
|
database: ecliptic_production
|
@@ -418,7 +421,7 @@ production:
|
|
418
421
|
ClickHouse.config do |config|
|
419
422
|
config.logger = Rails.logger
|
420
423
|
config.assign(Rails.application.config_for('click_house'))
|
421
|
-
end
|
424
|
+
end
|
422
425
|
```
|
423
426
|
|
424
427
|
```ruby
|
@@ -465,7 +468,7 @@ class CreateAdvertVisits < ActiveRecord::Migration[6.0]
|
|
465
468
|
end
|
466
469
|
end
|
467
470
|
|
468
|
-
def down
|
471
|
+
def down
|
469
472
|
ClickHouse.connection.drop_table('visits')
|
470
473
|
end
|
471
474
|
end
|
@@ -509,7 +512,7 @@ end
|
|
509
512
|
````
|
510
513
|
|
511
514
|
````ruby
|
512
|
-
# FAKE MODEL FOR ClickHouse
|
515
|
+
# FAKE MODEL FOR ClickHouse
|
513
516
|
class Visit < ClickHouseRecord
|
514
517
|
scope :with_os, -> { where.not(os_family_id: nil) }
|
515
518
|
end
|
@@ -517,12 +520,12 @@ end
|
|
517
520
|
Visit.with_os.select('COUNT(*) as counter').group(:ipv4).select_all
|
518
521
|
#=> [{ 'ipv4' => 1455869, 'counter' => 104 }]
|
519
522
|
|
520
|
-
Visit.with_os.select('COUNT(*)').select_value
|
523
|
+
Visit.with_os.select('COUNT(*)').select_value
|
521
524
|
#=> 20_345_678
|
522
|
-
|
523
|
-
Visit.where(user_id: 1).select_one
|
524
|
-
#=> { 'ipv4' => 1455869, 'user_id' => 1 }
|
525
|
-
````
|
525
|
+
|
526
|
+
Visit.where(user_id: 1).select_one
|
527
|
+
#=> { 'ipv4' => 1455869, 'user_id' => 1 }
|
528
|
+
````
|
526
529
|
|
527
530
|
## Using with RSpec
|
528
531
|
|
@@ -537,7 +540,7 @@ end
|
|
537
540
|
```
|
538
541
|
|
539
542
|
```ruby
|
540
|
-
RSpec.describe Api::MetricsCountroller, truncate_click_house: true do
|
543
|
+
RSpec.describe Api::MetricsCountroller, truncate_click_house: true do
|
541
544
|
it { }
|
542
545
|
it { }
|
543
546
|
end
|
data/click_house.gemspec
CHANGED
@@ -10,18 +10,16 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.summary = 'Modern Ruby database driver for ClickHouse'
|
11
11
|
spec.description = 'Yandex ClickHouse database interface for Ruby'
|
12
12
|
spec.homepage = 'https://github.com/shlima/click_house'
|
13
|
-
spec.required_ruby_version = '>= 2.
|
13
|
+
spec.required_ruby_version = '>= 2.6.0'
|
14
14
|
|
15
15
|
# Specify which files should be added to the gem when it is released.
|
16
16
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
17
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
18
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
19
|
end
|
20
|
-
spec.bindir = 'exe'
|
21
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
20
|
spec.require_paths = ['lib']
|
23
21
|
|
24
|
-
spec.add_dependency 'faraday'
|
22
|
+
spec.add_dependency 'faraday', '>= 1.7'
|
25
23
|
spec.add_dependency 'faraday_middleware'
|
26
24
|
spec.add_development_dependency 'bundler'
|
27
25
|
spec.add_development_dependency 'rake'
|
data/docker-compose.yml
CHANGED
data/lib/click_house/config.rb
CHANGED
@@ -15,7 +15,8 @@ module ClickHouse
|
|
15
15
|
timeout: nil,
|
16
16
|
open_timeout: nil,
|
17
17
|
ssl_verify: false,
|
18
|
-
headers: {}
|
18
|
+
headers: {},
|
19
|
+
global_params: {}
|
19
20
|
}.freeze
|
20
21
|
|
21
22
|
attr_accessor :adapter
|
@@ -31,6 +32,7 @@ module ClickHouse
|
|
31
32
|
attr_accessor :open_timeout
|
32
33
|
attr_accessor :ssl_verify
|
33
34
|
attr_accessor :headers
|
35
|
+
attr_accessor :global_params
|
34
36
|
|
35
37
|
def initialize(params = {})
|
36
38
|
assign(DEFAULTS.merge(params))
|
@@ -49,11 +51,15 @@ module ClickHouse
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def logger!
|
52
|
-
@logger ||
|
54
|
+
@logger || null_logger
|
53
55
|
end
|
54
56
|
|
55
57
|
def url!
|
56
58
|
@url || "#{scheme}://#{host}:#{port}"
|
57
59
|
end
|
60
|
+
|
61
|
+
def null_logger
|
62
|
+
@null_logger ||= Logger.new(IO::NULL)
|
63
|
+
end
|
58
64
|
end
|
59
65
|
end
|
@@ -17,16 +17,33 @@ module ClickHouse
|
|
17
17
|
@config = config
|
18
18
|
end
|
19
19
|
|
20
|
-
def execute(query, body = nil, database: config.database)
|
21
|
-
post(body, query: { query: query }, database: database)
|
20
|
+
def execute(query, body = nil, database: config.database, params: {})
|
21
|
+
post(body, query: { query: query }, database: database, params: config.global_params.merge(params))
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
# @param path [String] Clickhouse HTTP endpoint, e.g. /ping, /replica_status
|
25
|
+
# @param body [String] SQL to run
|
26
|
+
# @param database [String|NilClass] database to use, nil to skip
|
27
|
+
# @param query [Hash] other CH settings to send through params, e.g. max_rows_to_read=1
|
28
|
+
# @example get(body: 'select number from system.numbers limit 100', query: { max_rows_to_read: 10 })
|
29
|
+
# @return [Faraday::Response]
|
30
|
+
def get(path = '/', body: '', query: {}, database: config.database)
|
31
|
+
# backward compatibility since
|
32
|
+
# https://github.com/shlima/click_house/pull/12/files#diff-9c6f3f06d3b575731eae4b6b95ddbcdcc20452c432b8f6e87a3a8e8645818107R24
|
33
|
+
if query.is_a?(String)
|
34
|
+
query = { query: query }
|
35
|
+
config.logger!.warn('since v1.4.0 use connection.get(body: "SELECT 1") instead of connection.get(query: "SELECT 1")')
|
36
|
+
end
|
37
|
+
|
38
|
+
transport.get(path) do |conn|
|
39
|
+
conn.params = query.merge(database: database).compact
|
40
|
+
conn.params[:send_progress_in_http_headers] = 1 unless body.empty?
|
41
|
+
conn.body = body
|
42
|
+
end
|
26
43
|
end
|
27
44
|
|
28
|
-
def post(body = nil, query: {}, database: config.database)
|
29
|
-
transport.post(compose('/', query.merge(database: database)), body)
|
45
|
+
def post(body = nil, query: {}, database: config.database, params: {})
|
46
|
+
transport.post(compose('/', query.merge(database: database, **params)), body)
|
30
47
|
end
|
31
48
|
|
32
49
|
def transport
|
@@ -35,9 +52,9 @@ module ClickHouse
|
|
35
52
|
conn.options.open_timeout = config.open_timeout
|
36
53
|
conn.headers = config.headers
|
37
54
|
conn.ssl.verify = config.ssl_verify
|
38
|
-
conn.basic_auth
|
39
|
-
conn.response Middleware::Logging, logger: config.logger!
|
55
|
+
conn.request(:basic_auth, config.username, config.password) if config.auth?
|
40
56
|
conn.response Middleware::RaiseError
|
57
|
+
conn.response Middleware::Logging, logger: config.logger!
|
41
58
|
conn.response :json, content_type: %r{application/json}
|
42
59
|
conn.response Middleware::ParseCsv, content_type: %r{text/csv}
|
43
60
|
conn.adapter config.adapter
|
@@ -6,9 +6,11 @@ module ClickHouse
|
|
6
6
|
EXPLAIN = 'EXPLAIN'
|
7
7
|
EXPLAIN_RE = /\A(\s*#{EXPLAIN})/io.freeze
|
8
8
|
|
9
|
-
|
9
|
+
# @return String
|
10
|
+
def explain(sql, io: StringIO.new)
|
10
11
|
res = execute("#{EXPLAIN} #{sql.gsub(EXPLAIN_RE, '')}")
|
11
|
-
io
|
12
|
+
io.puts(res.body)
|
13
|
+
io.string
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
@@ -4,8 +4,7 @@ module ClickHouse
|
|
4
4
|
module Extend
|
5
5
|
module ConnectionHealthy
|
6
6
|
def ping
|
7
|
-
|
8
|
-
get(database: nil, query: { send_progress_in_http_headers: nil }).success?
|
7
|
+
get('/ping', database: nil).success?
|
9
8
|
end
|
10
9
|
|
11
10
|
def replicas_status
|
@@ -5,17 +5,17 @@ module ClickHouse
|
|
5
5
|
module ConnectionSelective
|
6
6
|
# @return [ResultSet]
|
7
7
|
def select_all(sql)
|
8
|
-
response =
|
8
|
+
response = get(body: Util::Statement.format(sql, 'JSON'))
|
9
9
|
Response::Factory[response]
|
10
10
|
end
|
11
11
|
|
12
12
|
def select_value(sql)
|
13
|
-
response =
|
13
|
+
response = get(body: Util::Statement.format(sql, 'JSON'))
|
14
14
|
Array(Response::Factory[response].first).dig(0, -1)
|
15
15
|
end
|
16
16
|
|
17
17
|
def select_one(sql)
|
18
|
-
response =
|
18
|
+
response = get(body: Util::Statement.format(sql, 'JSON'))
|
19
19
|
Response::Factory[response].first
|
20
20
|
end
|
21
21
|
end
|
@@ -29,11 +29,8 @@ module ClickHouse
|
|
29
29
|
# rubocop:disable Layout/LineLength
|
30
30
|
def on_complete(env)
|
31
31
|
summary = extract_summary(env.response_headers)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
logger.info("\e[1m[35mSQL (#{Util::Pretty.measure(elapsed)})\e[0m #{query};")
|
36
|
-
logger.debug(body) if body
|
32
|
+
logger.info("\e[1m[35mSQL (#{duration_stats_log(env.body)})\e[0m #{query(env)};")
|
33
|
+
logger.debug(body) if body && !query_in_body?(env)
|
37
34
|
logger.info("\e[1m[36mRead: #{summary.fetch(:read_rows)} rows, #{summary.fetch(:read_bytes)}. Written: #{summary.fetch(:written_rows)} rows, #{summary.fetch(:written_bytes)}\e[0m")
|
38
35
|
end
|
39
36
|
# rubocop:enable Layout/LineLength
|
@@ -46,6 +43,28 @@ module ClickHouse
|
|
46
43
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
47
44
|
end
|
48
45
|
|
46
|
+
def query_in_body?(env)
|
47
|
+
env.method == :get
|
48
|
+
end
|
49
|
+
|
50
|
+
def query(env)
|
51
|
+
if query_in_body?(env)
|
52
|
+
body
|
53
|
+
else
|
54
|
+
CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def duration_stats_log(body)
|
59
|
+
elapsed = duration
|
60
|
+
clickhouse_elapsed = body['statistics'].fetch('elapsed') if body.is_a?(Hash) && body.key?('statistics')
|
61
|
+
|
62
|
+
[
|
63
|
+
"Total: #{Util::Pretty.measure(elapsed * 1000)}",
|
64
|
+
("CH: #{Util::Pretty.measure(clickhouse_elapsed * 1000)}" if clickhouse_elapsed)
|
65
|
+
].compact.join(', ')
|
66
|
+
end
|
67
|
+
|
49
68
|
def extract_summary(headers)
|
50
69
|
JSON.parse(headers.fetch('x-clickhouse-summary', '{}')).tap do |summary|
|
51
70
|
summary[:read_rows] = summary['read_rows']
|
@@ -10,7 +10,12 @@ module ClickHouse
|
|
10
10
|
|
11
11
|
return body if !body.is_a?(Hash) || !(body.key?('meta') && body.key?('data'))
|
12
12
|
|
13
|
-
ResultSet.new(
|
13
|
+
ResultSet.new(
|
14
|
+
meta: body.fetch('meta'),
|
15
|
+
data: body.fetch('data'),
|
16
|
+
totals: body['totals'],
|
17
|
+
statistics: body['statistics']
|
18
|
+
)
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
@@ -14,7 +14,7 @@ module ClickHouse
|
|
14
14
|
:inspect, :each, :fetch, :length, :count, :size,
|
15
15
|
:first, :last, :[], :to_h
|
16
16
|
|
17
|
-
attr_reader :meta, :data, :statistics
|
17
|
+
attr_reader :meta, :data, :totals, :statistics
|
18
18
|
|
19
19
|
class << self
|
20
20
|
# @return [Array<String, Array>]
|
@@ -48,9 +48,13 @@ module ClickHouse
|
|
48
48
|
|
49
49
|
# @param meta [Array]
|
50
50
|
# @param data [Array]
|
51
|
-
|
51
|
+
# @param totals [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
|
52
|
+
# https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
|
53
|
+
# Hash in JSON format and Array in JSONCompact
|
54
|
+
def initialize(meta:, data:, totals: nil, statistics: nil)
|
52
55
|
@meta = meta
|
53
56
|
@data = data
|
57
|
+
@totals = totals
|
54
58
|
@statistics = Hash(statistics)
|
55
59
|
end
|
56
60
|
|
data/lib/click_house/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: click_house
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aliaksandr Shylau
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: faraday_middleware
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,14 +203,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
203
203
|
requirements:
|
204
204
|
- - ">="
|
205
205
|
- !ruby/object:Gem::Version
|
206
|
-
version: 2.
|
206
|
+
version: 2.6.0
|
207
207
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
208
|
requirements:
|
209
209
|
- - ">="
|
210
210
|
- !ruby/object:Gem::Version
|
211
211
|
version: '0'
|
212
212
|
requirements: []
|
213
|
-
rubygems_version: 3.
|
213
|
+
rubygems_version: 3.3.7
|
214
214
|
signing_key:
|
215
215
|
specification_version: 4
|
216
216
|
summary: Modern Ruby database driver for ClickHouse
|