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.
- 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
|