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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/Gemfile.lock +1 -1
- data/Gemfile_faraday1 +1 -1
- data/Gemfile_faraday1.lock +3 -4
- data/Gemfile_faraday2.lock +1 -1
- data/README.md +59 -35
- data/lib/click_house/benchmark/map_join.rb +20 -0
- data/lib/click_house/config.rb +29 -3
- data/lib/click_house/connection.rb +3 -2
- data/lib/click_house/errors.rb +1 -0
- data/lib/click_house/extend/connection_inserting.rb +62 -10
- data/lib/click_house/extend/connection_selective.rb +12 -3
- data/lib/click_house/extend/connection_table.rb +6 -1
- data/lib/click_house/middleware/logging.rb +23 -39
- data/lib/click_house/middleware/response_base.rb +4 -4
- data/lib/click_house/middleware/summary_middleware.rb +26 -0
- data/lib/click_house/middleware.rb +1 -0
- data/lib/click_house/response/factory.rb +37 -9
- data/lib/click_house/response/result_set.rb +94 -12
- data/lib/click_house/response/summary.rb +109 -0
- data/lib/click_house/response.rb +1 -1
- data/lib/click_house/serializer/base.rb +32 -0
- data/lib/click_house/serializer/json_oj_serializer.rb +17 -0
- data/lib/click_house/serializer/json_serializer.rb +11 -0
- data/lib/click_house/serializer.rb +9 -0
- data/lib/click_house/type/array_type.rb +4 -0
- data/lib/click_house/type/base_type.rb +4 -0
- data/lib/click_house/type/boolean_type.rb +6 -1
- data/lib/click_house/type/date_time64_type.rb +1 -1
- data/lib/click_house/type/date_time_type.rb +1 -1
- data/lib/click_house/type/decimal_type.rb +5 -4
- data/lib/click_house/type/string_type.rb +1 -1
- data/lib/click_house/util.rb +7 -0
- data/lib/click_house/version.rb +1 -1
- data/lib/click_house.rb +5 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 364117bb31fd3478fcf00d981b61205aa9fb864abbfeb9f9bdf253e53a0917fa
|
4
|
+
data.tar.gz: d0b0d7a21175a9c2e044a88351ddd6d107b1875769ae737d3e547e41e01d93d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Gemfile_faraday1
CHANGED
data/Gemfile_faraday1.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
click_house (2.
|
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
|
-
|
101
|
+
faraday (< 2)
|
103
102
|
oj
|
104
103
|
pry
|
105
104
|
rake
|
data/Gemfile_faraday2.lock
CHANGED
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
|
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',
|
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
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
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
|
-
```
|
349
|
-
|
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
|
-
|
363
|
-
ClickHouse.
|
364
|
-
|
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
|
data/lib/click_house/config.rb
CHANGED
@@ -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
|
69
|
-
conn.response
|
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
|
data/lib/click_house/errors.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
module ClickHouse
|
4
4
|
module Extend
|
5
5
|
module ConnectionInserting
|
6
|
-
|
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
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
70
|
+
raise ArgumentError, "unknown body class <#{body.class}>"
|
25
71
|
end
|
72
|
+
end
|
26
73
|
|
27
|
-
|
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}",
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
8
|
+
EMPTY = ''
|
9
|
+
GET = :get
|
9
10
|
|
10
|
-
attr_reader :logger, :starting
|
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(
|
18
|
+
def call(env)
|
18
19
|
@starting = timestamp
|
19
|
-
|
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 =
|
32
|
-
logger.info("\e[1m[35mSQL (#{duration_stats_log(
|
33
|
-
logger.debug(
|
34
|
-
logger.info("\e[1m[36mRead: #{summary.
|
25
|
+
summary = SummaryMiddleware.extract(env)
|
26
|
+
logger.info("\e[1m[35mSQL (#{duration_stats_log(summary)})\e[0m #{query(env)};")
|
27
|
+
logger.debug(env.request_body) if log_body?(env)
|
28
|
+
logger.info("\e[1m[36mRead: #{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
|
-
|
47
|
-
|
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
|
52
|
-
|
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(
|
59
|
-
|
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 :
|
8
|
+
attr_reader :options
|
9
9
|
attr_reader :content_type
|
10
10
|
|
11
|
-
def initialize(app = nil,
|
11
|
+
def initialize(app = nil, options: {}, content_type: nil, preserve_raw: false)
|
12
12
|
super(app)
|
13
|
-
@
|
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
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
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
|
21
|
-
|
22
|
-
|
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
|
-
@
|
27
|
-
@
|
28
|
-
|
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
|
-
|
43
|
-
|
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
|
data/lib/click_house/response.rb
CHANGED
@@ -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,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
|
@@ -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?
|
@@ -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 =
|
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
|
-
|
25
|
-
|
24
|
+
# @return [BigDecimal]
|
25
|
+
def serialize(value, precision = MAXIMUM, _scale = nil)
|
26
|
+
cast(value, precision)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/click_house/util.rb
CHANGED
data/lib/click_house/version.rb
CHANGED
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.
|
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-
|
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
|