click_house 2.0.2 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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