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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '095426f805a20574d725842799b3e09368ef4239bf8a262d4ca061ef7689cedd'
4
- data.tar.gz: 77098569ba1c05b236bc6b68118116c63985c1cc91bc49cdc5ae49c2550459f6
3
+ metadata.gz: 85fbf603cf09ee0a076a3ca0fa0add17c167919c458008a5616a4a7b064b0790
4
+ data.tar.gz: 95cf0f595c70a2943bba62a0d8568b4f432b3fdf27365b036377a66960733efe
5
5
  SHA512:
6
- metadata.gz: a71819d5ea5c61bf85907501e9b80ca713dc3e78e97a066a9f3e8fb1713dfc0a81b91aef0b12c51389afad53c518ccca913544be0f44d60182d02ecb722e0e1c
7
- data.tar.gz: 95fb2a123492cb6f816ed4c8b1984ec1ba21b1784eceb489543081616ad5d0a5947d56623be63c7e8d5f4586977f4c2999a0bb8c229d20519e8d1f8fc2365749
6
+ metadata.gz: 8a31451c3ed68aa5ff213cabaa51578723212fe43d0e63a1b58a1d68b135facece27f2d52025512d41862ba7495dcd05972f3a9cd8a4d01cc25916e2e502f42b
7
+ data.tar.gz: 0d78cbad863fcb89e8d58da4c10eeec60013877c5097480c0e6bc190c9698641f5ac95c116bd3b938f7ce927aa58ad10abae73729ce579149d40a51e529320c7
@@ -8,13 +8,13 @@ jobs:
8
8
 
9
9
  services:
10
10
  clickhouse:
11
- image: yandex/clickhouse-server:21.1.4
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, 2.5, 2.4]
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
@@ -1,5 +1,6 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
+ /.idea/
3
4
  /_yardoc/
4
5
  /coverage/
5
6
  /pkg/
data/.rubocop.yml CHANGED
@@ -13,9 +13,9 @@ AllCops:
13
13
  Bundler/OrderedGems:
14
14
  Enabled: false
15
15
 
16
- # ============================== Documentation ======================
17
- Style/Documentation:
18
- Enabled: false
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
- Lint/AmbiguousAssignment:
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
- * fizx decimal type casting [#11](https://github.com/shlima/click_house/issues/11)
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.4.0)
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.4.4)
14
- faraday (1.3.0)
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
- faraday_middleware (1.0.0)
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.20.1)
24
- parser (3.0.0.0)
41
+ parallel (1.22.1)
42
+ parser (3.1.1.0)
25
43
  ast (~> 2.4.1)
26
- pry (0.14.0)
44
+ pry (0.14.1)
27
45
  coderay (~> 1.1)
28
46
  method_source (~> 1.0)
29
- rainbow (3.0.0)
30
- rake (13.0.3)
31
- regexp_parser (2.0.3)
32
- rexml (3.2.4)
33
- rspec (3.10.0)
34
- rspec-core (~> 3.10.0)
35
- rspec-expectations (~> 3.10.0)
36
- rspec-mocks (~> 3.10.0)
37
- rspec-core (3.10.1)
38
- rspec-support (~> 3.10.0)
39
- rspec-expectations (3.10.1)
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.10.0)
42
- rspec-mocks (3.10.2)
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.10.0)
45
- rspec-support (3.10.2)
46
- rubocop (1.9.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.0.0.0)
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.2.0, < 2.0)
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.4.1)
56
- parser (>= 2.7.1.5)
57
- rubocop-performance (1.9.2)
58
- rubocop (>= 0.90.0, < 2.0)
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.4)
62
- unicode-display_width (2.0.0)
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.2.3
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.4.0'
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
@@ -2,7 +2,7 @@ version: '3.5'
2
2
 
3
3
  services:
4
4
  clickhouse:
5
- image: yandex/clickhouse-server:21.1.4
5
+ image: yandex/clickhouse-server:22.1
6
6
  ports:
7
7
  - "8123:8123"
8
8
  - "9000:9000"
@@ -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 || Logger.new(IO::NULL)
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
- def get(path = '/', query: {}, database: config.database)
25
- transport.get(compose(path, query.merge(database: database)))
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(config.username, config.password) if config.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
- def explain(sql, io: $stdout)
9
+ # @return String
10
+ def explain(sql, io: StringIO.new)
10
11
  res = execute("#{EXPLAIN} #{sql.gsub(EXPLAIN_RE, '')}")
11
- io << res.body
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
- # without +send_progress_in_http_headers: nil+ DB::Exception: Empty query returns
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 = execute(Util::Statement.format(sql, 'JSON'))
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 = execute(Util::Statement.format(sql, 'JSON'))
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 = execute(Util::Statement.format(sql, 'JSON'))
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
- elapsed = duration
33
- query = CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]'
34
-
35
- logger.info("\e[1mSQL (#{Util::Pretty.measure(elapsed)})\e[0m #{query};")
36
- logger.debug(body) if body
32
+ logger.info("\e[1mSQL (#{duration_stats_log(env.body)})\e[0m #{query(env)};")
33
+ logger.debug(body) if body && !query_in_body?(env)
37
34
  logger.info("\e[1mRead: #{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(meta: body.fetch('meta'), data: body.fetch('data'), statistics: body['statistics'])
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
- def initialize(meta:, data:, statistics: nil)
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
 
@@ -4,7 +4,7 @@ module ClickHouse
4
4
  module Type
5
5
  class FloatType < BaseType
6
6
  def cast(value)
7
- Float(value)
7
+ Float(value) unless value.nil?
8
8
  end
9
9
 
10
10
  def serialize(value)
@@ -4,7 +4,7 @@ module ClickHouse
4
4
  module Type
5
5
  class IntegerType < BaseType
6
6
  def cast(value)
7
- Integer(value)
7
+ Integer(value) unless value.nil?
8
8
  end
9
9
 
10
10
  def serialize(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClickHouse
4
- VERSION = '1.4.0'
4
+ VERSION = '1.6.1'
5
5
  end
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.0
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aliaksandr Shylau
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-15 00:00:00.000000000 Z
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: '0'
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: '0'
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.4.0
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.2.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