click_house 2.0.2 → 2.1.1

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile.lock +1 -1
  4. data/Gemfile_faraday1 +1 -1
  5. data/Gemfile_faraday1.lock +3 -4
  6. data/Gemfile_faraday2.lock +1 -1
  7. data/README.md +59 -35
  8. data/lib/click_house/benchmark/map_join.rb +20 -0
  9. data/lib/click_house/config.rb +29 -3
  10. data/lib/click_house/connection.rb +3 -2
  11. data/lib/click_house/errors.rb +1 -0
  12. data/lib/click_house/extend/connection_inserting.rb +62 -10
  13. data/lib/click_house/extend/connection_selective.rb +12 -3
  14. data/lib/click_house/extend/connection_table.rb +6 -1
  15. data/lib/click_house/middleware/logging.rb +23 -39
  16. data/lib/click_house/middleware/response_base.rb +4 -4
  17. data/lib/click_house/middleware/summary_middleware.rb +26 -0
  18. data/lib/click_house/middleware.rb +1 -0
  19. data/lib/click_house/response/factory.rb +37 -9
  20. data/lib/click_house/response/result_set.rb +94 -12
  21. data/lib/click_house/response/summary.rb +109 -0
  22. data/lib/click_house/response.rb +1 -1
  23. data/lib/click_house/serializer/base.rb +32 -0
  24. data/lib/click_house/serializer/json_oj_serializer.rb +17 -0
  25. data/lib/click_house/serializer/json_serializer.rb +11 -0
  26. data/lib/click_house/serializer.rb +9 -0
  27. data/lib/click_house/type/array_type.rb +4 -0
  28. data/lib/click_house/type/base_type.rb +4 -0
  29. data/lib/click_house/type/boolean_type.rb +6 -1
  30. data/lib/click_house/type/date_time64_type.rb +1 -1
  31. data/lib/click_house/type/date_time_type.rb +1 -1
  32. data/lib/click_house/type/decimal_type.rb +5 -4
  33. data/lib/click_house/type/string_type.rb +1 -1
  34. data/lib/click_house/util.rb +7 -0
  35. data/lib/click_house/version.rb +1 -1
  36. data/lib/click_house.rb +5 -0
  37. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2d62b43147b81ad3551c48c79388a949307d03491c1d87965d25ae9818e0a86
4
- data.tar.gz: ad6e79c3a974e4e39889d93dcabd94d3dc8f2bced9b739449f741bd1c8c83038
3
+ metadata.gz: 364117bb31fd3478fcf00d981b61205aa9fb864abbfeb9f9bdf253e53a0917fa
4
+ data.tar.gz: d0b0d7a21175a9c2e044a88351ddd6d107b1875769ae737d3e547e41e01d93d1
5
5
  SHA512:
6
- metadata.gz: 9035db18ecc851ab6de8f9ae88d434dfff07f2da5e23eac4cf3c091fe699871c64f0c9c05a1637730f44dfd5fef4eca60f6cd94f613360113152461f7e4dabe8
7
- data.tar.gz: bb93033b9ffc82a7003af0affea015ad2a84292a86a50c46ea6cecfa232b22d172708dc1633f896fd29eea373270eb464baa70e02df8d036195d0aeb2f88f29c
6
+ metadata.gz: 481ba8166f2300302c1a0836e336097e97a25015fbe5a5cf8e8fb5912b2170d50a2a1bc25178deb8b4a4b17d63e5638d0200bbca5a84c6a237de694ecf5f4d76
7
+ data.tar.gz: ba051b743929517452507d54f98de6d57de724b4400d1cf9db02666a48c5b00d4dbd00a3ffab6b5f2f9c3a7db78c15f84ae36523e2ee76946526fe131722ee05
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ # 2.1.1
2
+ * Fix logging with symbolized keys JSON
3
+ * Unknown formats return raw `Response::ResultSelt` like regular JSON query
4
+
5
+ # 2.1.0
6
+ * `ClickHouse.connection.insert` now returns `ClickHouse::Response::Summary` object
7
+ with methods `headers`, `summary`, `written_rows`, `written_bytes`, etc...
8
+ * `ClickHouse.connection.insert(columns: ["id"], values: [1])` now uses `JSONCompactEachRow` by default
9
+ (to increase JSON serialization speed)
10
+ * Methods `insert_rows` and `insert_compact` added to `connection`
11
+ * Added ability to pass object directly to insert like:
12
+ `ClickHouse.connection.insert("table", {id: 1})` or
13
+ `ClickHouse.connection.insert("table", [{id: 1})]` (for ruby < 3.0 use `ClickHouse.connection.insert("table", [{id: 1}], {})`)
14
+ * 🔥 Added config option `json_serializer` (one of `ClickHouse::Serializer::JsonSerializer`, `ClickHouse::Serializer::JsonOjSerializer`)
15
+ * 🔥 Added config option `symbolize_keys`
16
+ * 🔥 Added type serialization for INSERT statements, example below:
17
+
18
+ ```sql
19
+ CREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory
20
+ ```
21
+
22
+ ```ruby
23
+ # cache table schema in a class variable
24
+ @schema = ClickHouse.connection.table_schema('assets')
25
+
26
+ # Json each row
27
+ ClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))
28
+
29
+ # Json compact
30
+ ClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|
31
+ buffer << [
32
+ @schema.serialize_column("visible", true),
33
+ @schema.serialize_column("tags", ['ruby']),
34
+ ]
35
+ end
36
+ ```
37
+
1
38
  # 2.0.0
2
39
  * Fixed `Bigdecimal` casting with high precision
3
40
  * Added nested `type casting like Array(Array(Array(Nullable(T))))`
@@ -5,6 +42,7 @@
5
42
  * Added `Tuple(T1, T2)` support
6
43
  * Added support for `Faraday` v1 and v2
7
44
  * Added support for `Oj` parser
45
+ * Time types return `Time` class instead of `DateTime` for now
8
46
 
9
47
  # 1.6.3
10
48
  * [PR](https://github.com/shlima/click_house/pull/38) Add option format for insert
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.0.2)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
data/Gemfile_faraday1 CHANGED
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
7
  # lock faraday to v1
8
- gem 'faraday_middleware'
8
+ gem 'faraday', '< 2'
9
9
 
10
10
  # Specify your gem's dependencies in click_house.gemspec
11
11
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.0.1)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
@@ -40,8 +40,6 @@ GEM
40
40
  faraday-patron (1.0.0)
41
41
  faraday-rack (1.0.0)
42
42
  faraday-retry (1.0.3)
43
- faraday_middleware (1.2.0)
44
- faraday (~> 1.0)
45
43
  i18n (1.12.0)
46
44
  concurrent-ruby (~> 1.0)
47
45
  json (2.6.2)
@@ -94,12 +92,13 @@ GEM
94
92
  unicode-display_width (2.3.0)
95
93
 
96
94
  PLATFORMS
95
+ ruby
97
96
  x86_64-darwin-21
98
97
 
99
98
  DEPENDENCIES
100
99
  bundler
101
100
  click_house!
102
- faraday_middleware
101
+ faraday (< 2)
103
102
  oj
104
103
  pry
105
104
  rake
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (1.7.0)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
data/README.md CHANGED
@@ -52,6 +52,8 @@ ClickHouse.config do |config|
52
52
  config.timeout = 60
53
53
  config.open_timeout = 3
54
54
  config.ssl_verify = false
55
+ # set to true to symbolize keys for SELECT and INSERT statements (type casting)
56
+ config.symbolize_keys = false
55
57
  config.headers = {}
56
58
 
57
59
  # or provide connection options separately
@@ -66,10 +68,15 @@ ClickHouse.config do |config|
66
68
  # if you want to add settings to all queries
67
69
  config.global_params = { mutations_sync: 1 }
68
70
 
69
- # choose a ruby JSON parser
71
+ # choose a ruby JSON parser (default one)
70
72
  config.json_parser = ClickHouse::Middleware::ParseJson
71
73
  # or Oj parser
72
74
  config.json_parser = ClickHouse::Middleware::ParseJsonOj
75
+
76
+ # JSON.dump (default one)
77
+ config.json_serializer = ClickHouse::Serializer::JsonSerializer
78
+ # or Oj.dump
79
+ config.json_serializer = ClickHouse::Serializer::JsonOjSerializer
73
80
  end
74
81
  ```
75
82
 
@@ -131,12 +138,16 @@ Select all type-casted result set
131
138
  @result = ClickHouse.connection.select_all('SELECT * FROM visits')
132
139
 
133
140
  # all enumerable methods are delegated like #each, #map, #select etc
141
+ # results of #to_a is type casted
134
142
  @result.to_a #=> [{"date"=>#<Date: 2000-01-01>, "id"=>1}]
135
143
 
136
144
  # you can access raw data
137
145
  @result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
138
146
  @result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
139
147
  @result.statistics #=> {"elapsed"=>0.0002271, "rows_read"=>2, "bytes_read"=>12}
148
+ @result.summary #=> ClickHouse::Response::Summary
149
+ @result.headers #=> {"x-clickhouse-query-id"=>"9bf5f604-31fc-4eff-a4b5-277f2c71d199"}
150
+ @result.types #=> [Hash<String|Symbol, ClickHouse::Ast::Statement>]
140
151
  ```
141
152
 
142
153
  ### Select Value
@@ -195,7 +206,8 @@ response.body #=> "\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
195
206
 
196
207
  ## Insert
197
208
 
198
- When column names and values are transferred separately
209
+ When column names and values are transferred separately, data sends to the server
210
+ using `JSONCompactEachRow` format by default.
199
211
 
200
212
  ```ruby
201
213
  ClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|
@@ -203,23 +215,42 @@ ClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|
203
215
  buffer << [2, 'Venus']
204
216
  end
205
217
 
218
+ # or
206
219
  ClickHouse.connection.insert('table', columns: %i[id name], values: [[1, 'Mercury'], [2, 'Venus']])
207
- #=> true
208
220
  ```
209
221
 
210
- When rows are passed as a hash
211
-
222
+ When rows are passed as an Array or a Hash, data sends to the server
223
+ using `JSONEachRow` format by default.
212
224
 
213
225
  ```ruby
214
- ClickHouse.connection.insert('table', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])
226
+ ClickHouse.connection.insert('table', [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])
227
+
228
+ # or
229
+ ClickHouse.connection.insert('table', { name: 'Sun', id: 1 })
230
+
231
+ # for ruby < 3.0 provide an extra argument
232
+ ClickHouse.connection.insert('table', { name: 'Sun', id: 1 }, {})
215
233
 
234
+ # or
216
235
  ClickHouse.connection.insert('table') do |buffer|
217
236
  buffer << { name: 'Sun', id: 1 }
218
237
  buffer << { name: 'Moon', id: 2 }
219
238
  end
220
- #=> true
221
239
  ```
222
240
 
241
+ Sometimes it's needed to use other format than `JSONEachRow` For example if you want to send BigDecimal's
242
+ you could use `JSONStringsEachRow` format so string representation of `BigDecimal` will be parsed:
243
+
244
+ ```ruby
245
+ ClickHouse.connection.insert('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')
246
+ # or
247
+ ClickHouse.connection.insert_rows('table', { name: 'Sun', id: '1' }, format: 'JSONStringsEachRow')
248
+ # or
249
+ ClickHouse.connection.insert_compact('table', columns: %w[name id], values: %w[Sun 1], format: 'JSONCompactStringsEachRow')
250
+ ```
251
+
252
+ See the [type casting](#type-casting) section to insert the data in a proper way.
253
+
223
254
  ## Create a table
224
255
  ### Create table using DSL
225
256
 
@@ -326,42 +357,35 @@ end
326
357
  ## Type casting
327
358
 
328
359
  By default gem provides all necessary type casting, but you may overwrite or define
329
- your own logic
360
+ your own logic. if you need to redefine all built-in types with your implementation,
361
+ just clear the default type system:
330
362
 
331
363
  ```ruby
332
- class DateType
333
- def cast(value)
334
- Date.parse(value)
335
- end
336
-
337
- def serialize(value)
338
- value.strftime('%Y-%m-%d')
339
- end
340
- end
341
-
342
- ClickHouse.add_type('Date', DateType.new)
364
+ ClickHouse.types.clear
365
+ ClickHouse.types # => {}
366
+ ClickHouse.types.default #=> #<ClickHouse::Type::UndefinedType>
343
367
  ```
344
368
 
345
- If native type supports arguments, define *String* type with `%s`
346
- argument and *Numeric* type with `%d` argument:
369
+ Type casting works automatically when fetching data, when inserting data, you must serialize the types yourself
347
370
 
348
- ```ruby
349
- class DateTimeType
350
- def cast(value, time_zone)
351
- Time.parse("#{value} #{time_zone}")
352
- end
353
- end
354
-
355
- ClickHouse.add_type('DateTime(%s)', DateTimeType.new)
371
+ ```sql
372
+ CREATE TABLE assets(visible Boolean, tags Array(Nullable(String))) ENGINE Memory
356
373
  ```
357
374
 
358
- if you need to redefine all built-in types with your implementation,
359
- just clear the default type system:
360
-
361
375
  ```ruby
362
- ClickHouse.types.clear
363
- ClickHouse.types # => {}
364
- ClickHouse.types.default #=> #<ClickHouse::Type::UndefinedType:0x00007fc1cfabd630>
376
+ # cache table schema in a class variable
377
+ @schema = ClickHouse.connection.table_schema('assets')
378
+
379
+ # Json each row
380
+ ClickHouse.connection.insert('assets', @schema.serialize({'visible' => true, 'tags' => ['ruby']}))
381
+
382
+ # Json compact
383
+ ClickHouse.connection.insert('assets', columns: %w[visible tags]) do |buffer|
384
+ buffer << [
385
+ @schema.serialize_column("visible", true),
386
+ @schema.serialize_column("tags", ['ruby']),
387
+ ]
388
+ end
365
389
  ```
366
390
 
367
391
  ## Using with a connection pool
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'stringio'
5
+
6
+ INPUT = Array.new(5_000_000, 'foo bar')
7
+
8
+ Benchmark.bm do |x|
9
+ x.report('map.join') do
10
+ INPUT.map(&:to_s).join("\n")
11
+ end
12
+
13
+ x.report('StringIO') do
14
+ out = StringIO.new
15
+ INPUT.each do |value|
16
+ out << "#{value}\n"
17
+ end
18
+ out.string
19
+ end
20
+ end
@@ -18,18 +18,21 @@ module ClickHouse
18
18
  headers: {},
19
19
  global_params: {},
20
20
  json_parser: ClickHouse::Middleware::ParseJson,
21
+ json_serializer: ClickHouse::Serializer::JsonSerializer,
22
+ oj_dump_options: {
23
+ mode: :compat # to be able to dump improper JSON like {1 => 2}
24
+ },
21
25
  oj_load_options: {
22
26
  mode: :custom,
23
27
  allow_blank: true,
24
28
  bigdecimal_as_decimal: false, # dump BigDecimal as a String
25
29
  bigdecimal_load: :bigdecimal, # convert all decimal numbers to BigDecimal
26
- empty_string: false,
27
- second_precision: 6,
28
- time_format: :ruby,
29
30
  },
30
31
  json_load_options: {
31
32
  decimal_class: BigDecimal,
32
33
  },
34
+ # should be after json load options
35
+ symbolize_keys: false,
33
36
  }.freeze
34
37
 
35
38
  attr_accessor :adapter
@@ -49,6 +52,9 @@ module ClickHouse
49
52
  attr_accessor :oj_load_options
50
53
  attr_accessor :json_load_options
51
54
  attr_accessor :json_parser # response middleware
55
+ attr_accessor :oj_dump_options
56
+ attr_accessor :json_serializer # [ClickHouse::Serializer::Base]
57
+ attr_accessor :symbolize_keys # [NilClass, Boolean]
52
58
 
53
59
  def initialize(params = {})
54
60
  assign(DEFAULTS.merge(params))
@@ -77,5 +83,25 @@ module ClickHouse
77
83
  def null_logger
78
84
  @null_logger ||= Logger.new(IO::NULL)
79
85
  end
86
+
87
+ # @param klass [ClickHouse::Serializer::Base]
88
+ def json_serializer=(klass)
89
+ @json_serializer = klass.new(self)
90
+ end
91
+
92
+ def symbolize_keys=(value)
93
+ bool = value ? true : false
94
+
95
+ # merge to be able to clone a config
96
+ # prevent overriding default values
97
+ self.oj_load_options = oj_load_options.merge(symbol_keys: bool)
98
+ self.json_load_options = json_load_options.merge(symbolize_names: bool)
99
+ @symbolize_keys = bool
100
+ end
101
+
102
+ # @param name [Symbol, String]
103
+ def key(name)
104
+ symbolize_keys ? name.to_sym : name.to_s
105
+ end
80
106
  end
81
107
  end
@@ -65,8 +65,9 @@ module ClickHouse
65
65
 
66
66
  conn.response Middleware::RaiseError
67
67
  conn.response Middleware::Logging, logger: config.logger!
68
- conn.response config.json_parser, content_type: %r{application/json}, parser_options: { config: config }
69
- conn.response Middleware::ParseCsv, content_type: %r{text/csv}, parser_options: { config: config }
68
+ conn.response Middleware::SummaryMiddleware, options: { config: config } # should be after logger
69
+ conn.response config.json_parser, content_type: %r{application/json}, options: { config: config }
70
+ conn.response Middleware::ParseCsv, content_type: %r{text/csv}, options: { config: config }
70
71
  conn.adapter config.adapter
71
72
  end
72
73
  end
@@ -5,4 +5,5 @@ module ClickHouse
5
5
  NetworkException = Class.new(Error)
6
6
  DbException = Class.new(Error)
7
7
  StatementException = Class.new(Error)
8
+ SerializeError = Class.new(Error)
8
9
  end
@@ -3,7 +3,8 @@
3
3
  module ClickHouse
4
4
  module Extend
5
5
  module ConnectionInserting
6
- EMPTY_INSERT = true
6
+ DEFAULT_JSON_EACH_ROW_FORMAT = 'JSONEachRow'
7
+ DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT = 'JSONCompactEachRow'
7
8
 
8
9
  # @return [Boolean]
9
10
  #
@@ -13,20 +14,71 @@ module ClickHouse
13
14
  # buffer << ['Moon', 2]
14
15
  # end
15
16
  #
16
- # == Example with a param
17
- # subject.insert('rspec', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }], format: 'JSONStringsEachRow')
18
- def insert(table, columns: [], values: [], format: 'JSONEachRow')
19
- yield(values) if block_given?
17
+ # @return [Response::Execution]
18
+ # @param body [Array, Hash]
19
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
20
+ def insert(table, body = [], **opts)
21
+ # In Ruby < 3.0, if the last argument is a hash, and the method being called
22
+ # accepts keyword arguments, then it is always converted to keyword arguments.
23
+ columns = opts.fetch(:columns, [])
24
+ values = opts.fetch(:values, [])
25
+ format = opts.fetch(:format, nil)
26
+
27
+ yield(body) if block_given?
20
28
 
21
- body = if columns.empty?
22
- values.map(&:to_json)
29
+ # values: [{id: 1}]
30
+ if values.any? && columns.empty?
31
+ return insert_rows(table, values, format: format)
32
+ end
33
+
34
+ # body: [{id: 1}]
35
+ if body.any? && columns.empty?
36
+ return insert_rows(table, body, format: format)
37
+ end
38
+
39
+ # body: [1], columns: ["id"]
40
+ if body.any? && columns.any?
41
+ return insert_compact(table, columns: columns, values: body, format: format)
42
+ end
43
+
44
+ # columns: ["id"], values: [[1]]
45
+ if columns.any? && values.any?
46
+ return insert_compact(table, columns: columns, values: values, format: format)
47
+ end
48
+
49
+ Response::Factory.empty_exec(config)
50
+ end
51
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
52
+
53
+ # @param table [String]
54
+ # @param body [Array, Hash]
55
+ # @param format [String]
56
+ # @return [Response::Execution]
57
+ #
58
+ # Sometimes it's needed to use other format than JSONEachRow
59
+ # For example if you want to send BigDecimal's you could use
60
+ # JSONStringsEachRow format so string representation of BigDecimal will be parsed
61
+ def insert_rows(table, body, format: nil)
62
+ format ||= DEFAULT_JSON_EACH_ROW_FORMAT
63
+
64
+ case body
65
+ when Hash
66
+ Response::Factory.exec(execute("INSERT INTO #{table} FORMAT #{format}", config.json_serializer.dump(body)))
67
+ when Array
68
+ Response::Factory.exec(execute("INSERT INTO #{table} FORMAT #{format}", config.json_serializer.dump_each_row(body)))
23
69
  else
24
- values.map { |value_row| columns.zip(value_row).to_h.to_json }
70
+ raise ArgumentError, "unknown body class <#{body.class}>"
25
71
  end
72
+ end
26
73
 
27
- return EMPTY_INSERT if values.empty?
74
+ # @return [Response::Execution]
75
+ def insert_compact(table, columns: [], values: [], format: nil)
76
+ format ||= DEFAULT_JSON_COMPACT_EACH_ROW_FORMAT
77
+
78
+ yield(values) if block_given?
28
79
 
29
- execute("INSERT INTO #{table} FORMAT #{format}", body.join("\n")).success?
80
+ response = execute("INSERT INTO #{table} (#{columns.join(',')}) FORMAT #{format}", config.json_serializer.dump_each_row(values))
81
+ Response::Factory.exec(response)
30
82
  end
31
83
  end
32
84
  end
@@ -6,17 +6,26 @@ module ClickHouse
6
6
  # @return [ResultSet]
7
7
  def select_all(sql)
8
8
  response = get(body: sql, query: { default_format: 'JSON' })
9
- Response::Factory[response]
9
+ Response::Factory.response(response, config)
10
10
  end
11
11
 
12
12
  def select_value(sql)
13
13
  response = get(body: sql, query: { default_format: 'JSON' })
14
- Array(Response::Factory[response].first).dig(0, -1)
14
+ got = Response::Factory.response(response, config).first
15
+
16
+ case got
17
+ when Hash
18
+ Array(got).dig(0, -1) # get a value of a first key for JSON format
19
+ when Array
20
+ got[0] # for CSV format
21
+ else
22
+ got # for RowBinary format
23
+ end
15
24
  end
16
25
 
17
26
  def select_one(sql)
18
27
  response = get(body: sql, query: { default_format: 'JSON' })
19
- Response::Factory[response].first
28
+ Response::Factory.response(response, config).first
20
29
  end
21
30
  end
22
31
  end
@@ -10,7 +10,12 @@ module ClickHouse
10
10
 
11
11
  # @return [ResultSet]
12
12
  def describe_table(name)
13
- Response::Factory[execute("DESCRIBE TABLE #{name} FORMAT JSON")]
13
+ Response::Factory.response(execute("DESCRIBE TABLE #{name} FORMAT JSON"), config)
14
+ end
15
+
16
+ # @return [ResultSet]
17
+ def table_schema(name)
18
+ Response::Factory.response(execute("SELECT * FROM #{name} WHERE 1=0 FORMAT JSON"), config)
14
19
  end
15
20
 
16
21
  # @return [Boolean]
@@ -5,36 +5,32 @@ module ClickHouse
5
5
  class Logging < Faraday::Middleware
6
6
  Faraday::Response.register_middleware self => self
7
7
 
8
- SUMMARY_HEADER = 'x-clickhouse-summary'
8
+ EMPTY = ''
9
+ GET = :get
9
10
 
10
- attr_reader :logger, :starting, :body
11
+ attr_reader :logger, :starting
11
12
 
12
13
  def initialize(app = nil, logger:)
13
14
  @logger = logger
14
15
  super(app)
15
16
  end
16
17
 
17
- def call(environment)
18
+ def call(env)
18
19
  @starting = timestamp
19
- @body = environment.body if log_body?
20
- @app.call(environment).on_complete(&method(:on_complete))
21
- end
22
-
23
- private
24
-
25
- def log_body?
26
- logger.level == Logger::DEBUG
20
+ super
27
21
  end
28
22
 
29
23
  # rubocop:disable Layout/LineLength
30
24
  def on_complete(env)
31
- summary = extract_summary(env.response_headers)
32
- logger.info("\e[1mSQL (#{duration_stats_log(env.body)})\e[0m #{query(env)};")
33
- logger.debug(body) if body
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")
25
+ summary = SummaryMiddleware.extract(env)
26
+ logger.info("\e[1mSQL (#{duration_stats_log(summary)})\e[0m #{query(env)};")
27
+ logger.debug(env.request_body) if log_body?(env)
28
+ logger.info("\e[1mRead: #{summary.read_rows} rows, #{summary.read_bytes_pretty}. Written: #{summary.written_rows} rows, #{summary.written_bytes_pretty}\e[0m")
35
29
  end
36
30
  # rubocop:enable Layout/LineLength
37
31
 
32
+ private
33
+
38
34
  def duration
39
35
  timestamp - starting
40
36
  end
@@ -43,37 +39,25 @@ module ClickHouse
43
39
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
40
  end
45
41
 
46
- def query_in_body?(env)
47
- env.method == :get
42
+ # @return [Boolean]
43
+ def log_body?(env)
44
+ return unless logger.debug?
45
+ return if env.method == GET # GET queries logs body as a statement
46
+ return if env.request_body.nil? || env.request_body == EMPTY
47
+
48
+ true
48
49
  end
49
50
 
50
51
  def query(env)
51
- if query_in_body?(env)
52
- body
52
+ if env.method == GET
53
+ env.request_body
53
54
  else
54
- CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]'
55
+ String(CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]').chomp
55
56
  end
56
57
  end
57
58
 
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
-
68
- def extract_summary(headers)
69
- JSON.parse(headers.fetch('x-clickhouse-summary', '{}')).tap do |summary|
70
- summary[:read_rows] = summary['read_rows']
71
- summary[:read_bytes] = Util::Pretty.size(summary['read_bytes'].to_i)
72
- summary[:written_rows] = summary['written_rows']
73
- summary[:written_bytes] = Util::Pretty.size(summary['written_bytes'].to_i)
74
- end
75
- rescue JSON::ParserError
76
- {}
59
+ def duration_stats_log(summary)
60
+ "Total: #{Util::Pretty.measure(duration * 1000)}, CH: #{summary.elapsed_pretty}"
77
61
  end
78
62
  end
79
63
  end
@@ -5,12 +5,12 @@ module ClickHouse
5
5
  class ResponseBase < Faraday::Middleware
6
6
  CONTENT_TYPE_HEADER = 'content-type'
7
7
 
8
- attr_reader :parser_options
8
+ attr_reader :options
9
9
  attr_reader :content_type
10
10
 
11
- def initialize(app = nil, parser_options: {}, content_type: nil, preserve_raw: false)
11
+ def initialize(app = nil, options: {}, content_type: nil, preserve_raw: false)
12
12
  super(app)
13
- @parser_options = parser_options
13
+ @options = options
14
14
  @content_type = content_type
15
15
  @preserve_raw = preserve_raw
16
16
  on_setup
@@ -32,7 +32,7 @@ module ClickHouse
32
32
 
33
33
  # @return [Config]
34
34
  def config
35
- parser_options.fetch(:config)
35
+ options.fetch(:config)
36
36
  end
37
37
 
38
38
  private
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Middleware
5
+ class SummaryMiddleware < ResponseBase
6
+ Faraday::Response.register_middleware self => self
7
+
8
+ KEY = :summary
9
+
10
+ # @param env [Faraday::Env]
11
+ # @return [Response::Summary]
12
+ def self.extract(env)
13
+ env.custom_members.fetch(KEY)
14
+ end
15
+
16
+ # @param env [Faraday::Env]
17
+ def on_complete(env)
18
+ env.custom_members[KEY] = Response::Summary.new(
19
+ config,
20
+ headers: env.response_headers,
21
+ body: env.body.is_a?(Hash) ? env.body : {}
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,6 +3,7 @@
3
3
  module ClickHouse
4
4
  module Middleware
5
5
  autoload :ResponseBase, 'click_house/middleware/response_base'
6
+ autoload :SummaryMiddleware, 'click_house/middleware/summary_middleware'
6
7
  autoload :Logging, 'click_house/middleware/logging'
7
8
  autoload :ParseCsv, 'click_house/middleware/parse_csv'
8
9
  autoload :ParseJsonOj, 'click_house/middleware/parse_json_oj'
@@ -3,21 +3,49 @@
3
3
  module ClickHouse
4
4
  module Response
5
5
  class Factory
6
- # @return [String, ResultSet]
7
- # @params env [Faraday::Response]
8
- def self.[](faraday)
6
+ KEY_META = 'meta'
7
+ KEY_DATA = 'data'
8
+
9
+ # @return [ResultSet]
10
+ # @params faraday [Faraday::Response]
11
+ # @params config [Config]
12
+ def self.response(faraday, config)
9
13
  body = faraday.body
10
14
 
11
- return body if !body.is_a?(Hash) || !(body.key?('meta') && body.key?('data'))
15
+ # wrap to be able to use connection#select_one, connection#select_value
16
+ # with other formats like binary
17
+ return raw(faraday, config) unless body.is_a?(Hash)
18
+ return raw(faraday, config) unless body.key?(config.key(KEY_META)) && body.key?(config.key(KEY_DATA))
12
19
 
13
20
  ResultSet.new(
14
- meta: body.fetch('meta'),
15
- data: body.fetch('data'),
16
- totals: body['totals'],
17
- statistics: body['statistics'],
18
- rows_before_limit_at_least: body['rows_before_limit_at_least']
21
+ config: config,
22
+ meta: body.fetch(config.key(KEY_META)),
23
+ data: body.fetch(config.key(KEY_DATA)),
24
+ summary: Middleware::SummaryMiddleware.extract(faraday.env)
25
+ )
26
+ end
27
+
28
+ # @return [ResultSet]
29
+ # Rae ResultSet (without type casting)
30
+ def self.raw(faraday, config)
31
+ ResultSet.raw(
32
+ config: config,
33
+ data: Util.array(faraday.body),
34
+ summary: Middleware::SummaryMiddleware.extract(faraday.env)
19
35
  )
20
36
  end
37
+
38
+ # Result of execution
39
+ # @return [Response::Summary]
40
+ # @params faraday [Faraday::Response]
41
+ def self.exec(faraday)
42
+ Middleware::SummaryMiddleware.extract(faraday.env)
43
+ end
44
+
45
+ # @return [Response::Summary]
46
+ def self.empty_exec(config)
47
+ Summary.new(config)
48
+ end
21
49
  end
22
50
  end
23
51
  end
@@ -6,26 +6,69 @@ module ClickHouse
6
6
  extend Forwardable
7
7
  include Enumerable
8
8
 
9
- PLACEHOLDER_D = '%d'
10
- PLACEHOLDER_S = '%s'
9
+ KEY_META_NAME = 'name'
10
+ KEY_META_TYPE = 'type'
11
11
 
12
12
  def_delegators :to_a,
13
13
  :inspect, :each, :fetch, :length, :count, :size,
14
14
  :first, :last, :[], :to_h
15
15
 
16
- attr_reader :meta, :data, :totals, :statistics, :rows_before_limit_at_least
16
+ def_delegators :summary,
17
+ :statistics, :headers,
18
+ :totals, :rows_before_limit_at_least
17
19
 
20
+ attr_reader :config, :meta, :data, :summary
21
+
22
+ class << self
23
+ # @param config [Config]
24
+ # @return [ResultSet]
25
+ def raw(config:, data:, summary:)
26
+ new(config: config, data: data, to_a: data, meta: [], summary: summary)
27
+ end
28
+ end
29
+
30
+ # @param config [Config]
18
31
  # @param meta [Array]
19
32
  # @param data [Array]
20
- # @param totals [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
21
- # https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
22
- # Hash in JSON format and Array in JSONCompact
23
- def initialize(meta:, data:, totals: nil, statistics: nil, rows_before_limit_at_least: nil)
33
+ # @param summary [Response::Summary]
34
+ def initialize(config:, meta:, data:, summary:, to_a: nil)
35
+ @config = config
24
36
  @meta = meta
25
37
  @data = data
26
- @totals = totals
27
- @rows_before_limit_at_least = rows_before_limit_at_least
28
- @statistics = Hash(statistics)
38
+ @summary = summary
39
+ @to_a = to_a
40
+ end
41
+
42
+ # @return [Array, Hash]
43
+ # @param data [Array, Hash]
44
+ def serialize(data)
45
+ case data
46
+ when Hash
47
+ serialize_one(data)
48
+ when Array
49
+ data.map(&method(:serialize_one))
50
+ else
51
+ raise ArgumentError, "expect Hash or Array, got: #{data.class}"
52
+ end
53
+ end
54
+
55
+ # @return [Hash]
56
+ # @param row [Hash]
57
+ def serialize_one(row)
58
+ row.each_with_object({}) do |(key, value), object|
59
+ object[key] = serialize_column(key, value)
60
+ end
61
+ end
62
+
63
+ # @param name [String] column name
64
+ # @param value [Any]
65
+ def serialize_column(name, value)
66
+ stmt = types.fetch(name)
67
+ serialize_type(stmt, value)
68
+ rescue KeyError => e
69
+ raise SerializeError, "field <#{name}> does not exists in table schema: #{types}", e.backtrace
70
+ rescue StandardError => e
71
+ raise SerializeError, "failed to serialize <#{name}> with #{stmt}, #{e.class}, #{e.message}", e.backtrace
29
72
  end
30
73
 
31
74
  def to_a
@@ -39,8 +82,11 @@ module ClickHouse
39
82
  # @return [Hash<String, Ast::Statement>]
40
83
  def types
41
84
  @types ||= meta.each_with_object({}) do |row, object|
42
- object[row.fetch('name')] = begin
43
- current = Ast::Parser.new(row.fetch('type')).parse
85
+ column = row.fetch(config.key(KEY_META_NAME))
86
+ # make symbol keys, if config.symbolize_keys is true,
87
+ # to be able to cast and serialize properly
88
+ object[config.key(column)] = begin
89
+ current = Ast::Parser.new(row.fetch(config.key(KEY_META_TYPE))).parse
44
90
  assign_type(current)
45
91
  current
46
92
  end
@@ -96,6 +142,42 @@ module ClickHouse
96
142
  cast_type(stmt.arguments.fetch(ix), item)
97
143
  end
98
144
  end
145
+
146
+ # @param stmt [Ast::Statement]
147
+ def serialize_type(stmt, value)
148
+ return serialize_container(stmt, value) if stmt.caster.container?
149
+ return serialize_map(stmt, value) if stmt.caster.map?
150
+ return serialize_tuple(stmt, Array(value)) if stmt.caster.tuple?
151
+
152
+ stmt.caster.serialize(value, *stmt.arguments.map(&:value))
153
+ end
154
+
155
+ # @param stmt [Ast::Statement]
156
+ def serialize_container(stmt, value)
157
+ stmt.caster.cast_each(value) do |item|
158
+ # TODO: raise an error if multiple arguments
159
+ serialize_type(stmt.arguments.first, item)
160
+ end
161
+ end
162
+
163
+ # @return [Hash]
164
+ # @param stmt [Ast::Statement]
165
+ # @param hash [Hash]
166
+ def serialize_map(stmt, hash)
167
+ raise ArgumentError, "expect hash got #{hash.class}" unless hash.is_a?(Hash)
168
+
169
+ key_type, value_type = stmt.arguments
170
+ hash.each_with_object({}) do |(key, value), object|
171
+ object[serialize_type(key_type, key)] = serialize_type(value_type, value)
172
+ end
173
+ end
174
+
175
+ # @param stmt [Ast::Statement]
176
+ def serialize_tuple(stmt, value)
177
+ value.map.with_index do |item, ix|
178
+ serialize_type(stmt.arguments.fetch(ix), item)
179
+ end
180
+ end
99
181
  end
100
182
  end
101
183
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Response
5
+ class Summary
6
+ SUMMARY_HEADER = 'x-clickhouse-summary'
7
+ KEY_TOTALS = 'totals'
8
+ KEY_STATISTICS = 'statistics'
9
+ KEY_ROWS_BEFORE_LIMIT_AT_LEAST = 'rows_before_limit_at_least'
10
+ KEY_STAT_ELAPSED = 'elapsed'
11
+
12
+ attr_reader :config,
13
+ :headers,
14
+ :summary,
15
+ # {:elapsed=>0.387287e-3, :rows_read=>0, :bytes_read=>0}}
16
+ :statistics,
17
+ :totals,
18
+ :rows_before_limit_at_least
19
+
20
+ # @param config [Config]
21
+ # @param headers [Faraday::Utils::Headers]
22
+ # @param body [Hash]
23
+ # TOTALS [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
24
+ # https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
25
+ # Hash in JSON format and Array in JSONCompact
26
+ def initialize(config, headers: Faraday::Utils::Headers.new, body: {})
27
+ @headers = headers
28
+ @config = config
29
+ @statistics = body.fetch(config.key(KEY_STATISTICS), {})
30
+ @totals = body[config.key(KEY_TOTALS)]
31
+ @rows_before_limit_at_least = body[config.key(KEY_ROWS_BEFORE_LIMIT_AT_LEAST)]
32
+ @summary = parse_summary(headers[SUMMARY_HEADER])
33
+ end
34
+
35
+ # @return [Integer]
36
+ def read_rows
37
+ summary[config.key('read_rows')].to_i
38
+ end
39
+
40
+ # @return [Integer]
41
+ def read_bytes
42
+ summary[config.key('read_bytes')].to_i
43
+ end
44
+
45
+ # @return [String]
46
+ def read_bytes_pretty
47
+ Util::Pretty.size(read_bytes)
48
+ end
49
+
50
+ # @return [Integer]
51
+ def written_rows
52
+ summary[config.key('written_rows')].to_i
53
+ end
54
+
55
+ # @return [Integer]
56
+ def written_bytes
57
+ summary[config.key('written_bytes')].to_i
58
+ end
59
+
60
+ # @return [String]
61
+ def written_bytes_pretty
62
+ Util::Pretty.size(written_bytes)
63
+ end
64
+
65
+ # @return [Integer]
66
+ def total_rows_to_read
67
+ summary[config.key('total_rows_to_read')].to_i
68
+ end
69
+
70
+ # @return [Integer]
71
+ def result_rows
72
+ summary[config.key('result_rows')].to_i
73
+ end
74
+
75
+ # @return [Integer]
76
+ def result_bytes
77
+ summary[config.key('result_bytes')].to_i
78
+ end
79
+
80
+ # @return [Float]
81
+ def elapsed
82
+ statistics[config.key(KEY_STAT_ELAPSED)].to_f
83
+ end
84
+
85
+ # @return [String]
86
+ def elapsed_pretty
87
+ Util::Pretty.measure(elapsed * 1000)
88
+ end
89
+
90
+ private
91
+
92
+ # @return [Hash]
93
+ # {
94
+ # "read_rows" => "1",
95
+ # "read_bytes" => "23",
96
+ # "written_rows" => "1",
97
+ # "written_bytes" => "23",
98
+ # "total_rows_to_read" => "0",
99
+ # "result_rows" => "1",
100
+ # "result_bytes" => "23",
101
+ # }
102
+ def parse_summary(value)
103
+ return {} if value.nil? || value.empty?
104
+
105
+ JSON.parse(value)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -4,6 +4,6 @@ module ClickHouse
4
4
  module Response
5
5
  autoload :Factory, 'click_house/response/factory'
6
6
  autoload :ResultSet, 'click_house/response/result_set'
7
- autoload :Tokenize, 'click_house/response/tokenize'
7
+ autoload :Summary, 'click_house/response/summary'
8
8
  end
9
9
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Serializer
5
+ class Base
6
+ attr_reader :config
7
+
8
+ # @param config [Config]
9
+ def initialize(config)
10
+ @config = config
11
+ on_setup
12
+ end
13
+
14
+ def dump(data)
15
+ raise NotImplementedError, __method__
16
+ end
17
+
18
+ # @return [String]
19
+ # @param data [Array]
20
+ def dump_each_row(data, sep = "\n")
21
+ data.map(&method(:dump)).join(sep)
22
+ end
23
+
24
+ private
25
+
26
+ # require external dependencies here
27
+ def on_setup
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Serializer
5
+ class JsonOjSerializer < Base
6
+ def dump(data)
7
+ Oj.dump(data, config.oj_dump_options)
8
+ end
9
+
10
+ private
11
+
12
+ def on_setup
13
+ require 'oj' unless defined?(Oj)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Serializer
5
+ class JsonSerializer < Base
6
+ def dump(data)
7
+ JSON.dump(data)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Serializer
5
+ autoload :Base, 'click_house/serializer/base'
6
+ autoload :JsonSerializer, 'click_house/serializer/json_serializer'
7
+ autoload :JsonOjSerializer, 'click_house/serializer/json_oj_serializer'
8
+ end
9
+ end
@@ -7,6 +7,10 @@ module ClickHouse
7
7
  value.map(&block)
8
8
  end
9
9
 
10
+ def serialize_each(value, *_argv, &block)
11
+ value.map(&block)
12
+ end
13
+
10
14
  def container?
11
15
  true
12
16
  end
@@ -11,6 +11,10 @@ module ClickHouse
11
11
  raise NotImplementedError, __method__
12
12
  end
13
13
 
14
+ def serialize_each(_value, *)
15
+ raise NotImplementedError, __method__
16
+ end
17
+
14
18
  # @return [Boolean]
15
19
  # true if type contains another type like Nullable(T) or Array(T)
16
20
  def container?
@@ -7,7 +7,12 @@ module ClickHouse
7
7
  FALSE_VALUE = 0
8
8
 
9
9
  def cast(value)
10
- value.to_i == TRUE_VALUE
10
+ case value
11
+ when TrueClass, FalseClass
12
+ value
13
+ else
14
+ value.to_i == TRUE_VALUE
15
+ end
11
16
  end
12
17
 
13
18
  def serialize(value)
@@ -11,7 +11,7 @@ module ClickHouse
11
11
  end
12
12
  end
13
13
 
14
- def serialize(value, precision = 3)
14
+ def serialize(value, precision = 3, _tz = nil)
15
15
  value.strftime("%Y-%m-%d %H:%M:%S.%#{precision}N")
16
16
  end
17
17
  end
@@ -11,7 +11,7 @@ module ClickHouse
11
11
  end
12
12
  end
13
13
 
14
- def serialize(value)
14
+ def serialize(value, *)
15
15
  value.strftime('%Y-%m-%d %H:%M:%S')
16
16
  end
17
17
  end
@@ -3,14 +3,14 @@
3
3
  module ClickHouse
4
4
  module Type
5
5
  class DecimalType < BaseType
6
- MAXIMUM = Float::DIG
6
+ MAXIMUM = Float::DIG.next
7
7
 
8
8
  # clickhouse:
9
9
  # P - precision. Valid range: [ 1 : 76 ]. Determines how many decimal digits number can have (including fraction).
10
10
  # S - scale. Valid range: [ 0 : P ]. Determines how many decimal digits fraction can have.
11
11
  #
12
12
  # when Oj parser @refs https://stackoverflow.com/questions/47885304/deserialise-json-numbers-as-bigdecimal
13
- def cast(value, precision = Float::DIG, _scale = nil)
13
+ def cast(value, precision = MAXIMUM, _scale = nil)
14
14
  case value
15
15
  when BigDecimal
16
16
  value
@@ -21,8 +21,9 @@ module ClickHouse
21
21
  end
22
22
  end
23
23
 
24
- def serialize(value, precision = Float::DIG, _scale = nil)
25
- BigDecimal(value, precision.to_i).to_f unless value.nil?
24
+ # @return [BigDecimal]
25
+ def serialize(value, precision = MAXIMUM, _scale = nil)
26
+ cast(value, precision)
26
27
  end
27
28
  end
28
29
  end
@@ -7,7 +7,7 @@ module ClickHouse
7
7
  value.to_s unless value.nil?
8
8
  end
9
9
 
10
- def serialize(value)
10
+ def serialize(value, *)
11
11
  value.to_s unless value.nil?
12
12
  end
13
13
  end
@@ -4,5 +4,12 @@ module ClickHouse
4
4
  module Util
5
5
  autoload :Statement, 'click_house/util/statement'
6
6
  autoload :Pretty, 'click_house/util/pretty'
7
+
8
+ module_function
9
+
10
+ # wraps
11
+ def array(input)
12
+ input.is_a?(Array) ? input : [input]
13
+ end
7
14
  end
8
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClickHouse
4
- VERSION = '2.0.2'
4
+ VERSION = '2.1.1'
5
5
  end
data/lib/click_house.rb CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/time/calculations'
12
12
  require 'click_house/version'
13
13
  require 'click_house/errors'
14
14
  require 'click_house/response'
15
+ require 'click_house/serializer'
15
16
  require 'click_house/type'
16
17
  require 'click_house/middleware'
17
18
  require 'click_house/extend'
@@ -33,6 +34,10 @@ module ClickHouse
33
34
  add_type 'LowCardinality', Type::LowCardinalityType.new
34
35
  add_type 'Tuple', Type::TupleType.new
35
36
 
37
+ %w[Bool].each do |column|
38
+ add_type column, Type::BooleanType.new
39
+ end
40
+
36
41
  %w[Date].each do |column|
37
42
  add_type column, Type::DateType.new
38
43
  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: 2.0.2
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aliaksandr Shylau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-19 00:00:00.000000000 Z
11
+ date: 2022-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -175,6 +175,7 @@ files:
175
175
  - lib/click_house/ast/parser.rb
176
176
  - lib/click_house/ast/statement.rb
177
177
  - lib/click_house/ast/ticker.rb
178
+ - lib/click_house/benchmark/map_join.rb
178
179
  - lib/click_house/config.rb
179
180
  - lib/click_house/connection.rb
180
181
  - lib/click_house/definition.rb
@@ -199,9 +200,15 @@ files:
199
200
  - lib/click_house/middleware/parse_json_oj.rb
200
201
  - lib/click_house/middleware/raise_error.rb
201
202
  - lib/click_house/middleware/response_base.rb
203
+ - lib/click_house/middleware/summary_middleware.rb
202
204
  - lib/click_house/response.rb
203
205
  - lib/click_house/response/factory.rb
204
206
  - lib/click_house/response/result_set.rb
207
+ - lib/click_house/response/summary.rb
208
+ - lib/click_house/serializer.rb
209
+ - lib/click_house/serializer/base.rb
210
+ - lib/click_house/serializer/json_oj_serializer.rb
211
+ - lib/click_house/serializer/json_serializer.rb
205
212
  - lib/click_house/type.rb
206
213
  - lib/click_house/type/array_type.rb
207
214
  - lib/click_house/type/base_type.rb