click_house 2.1.0 → 2.1.2
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/.rubocop.yml +1 -0
- data/CHANGELOG.md +6 -1
- data/Gemfile.lock +1 -1
- data/Gemfile_faraday1.lock +1 -1
- data/Gemfile_faraday2.lock +1 -1
- data/README.md +8 -1
- data/lib/click_house/ast/statement.rb +11 -0
- data/lib/click_house/benchmark/casting.rb +45 -0
- data/lib/click_house/connection.rb +3 -2
- data/lib/click_house/extend/connection_inserting.rb +1 -1
- data/lib/click_house/extend/connection_selective.rb +10 -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 +22 -14
- data/lib/click_house/response/result_set.rb +22 -17
- data/lib/click_house/response/summary.rb +109 -0
- data/lib/click_house/response.rb +1 -1
- data/lib/click_house/type/date_time64_type.rb +25 -4
- data/lib/click_house/type/date_time_type.rb +5 -3
- data/lib/click_house/type/date_type.rb +4 -2
- data/lib/click_house/type/integer_type.rb +2 -2
- data/lib/click_house/type/ip_type.rb +2 -2
- data/lib/click_house/type/low_cardinality_type.rb +5 -1
- data/lib/click_house/type/nullable_type.rb +4 -0
- data/lib/click_house/type/string_type.rb +2 -2
- data/lib/click_house/util.rb +7 -0
- data/lib/click_house/version.rb +1 -1
- metadata +5 -3
- data/lib/click_house/response/execution.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f311abaedb0ef60cbfc25cabaae1223d266b9e5b83f677b1267c5e8b1b56048e
|
4
|
+
data.tar.gz: 58696f010c09d829038670e703126d28be343471b45fed0c07a05727d7ba0e95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35b8b80d3beca3ed584f29dd532d7a4e525dbc355d1cca5bca3d21d32f294ed6ba95b24ef01a6d4930ef195cbe9ab98ee0517a9620ffb1aa45c06353ddf557f1
|
7
|
+
data.tar.gz: 4ba73ab43c95300c19994e90b2ee7cade877490655ec07962c1dae1175968e36eb51a5711fe2ea8da49a934acacf6414d02052e9f6594914c591be8d6cac43fe
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
# 2.1.1
|
2
|
+
* Fix logging with symbolized keys JSON
|
3
|
+
* Unknown formats return raw `Response::ResultSelt` like regular JSON query
|
4
|
+
* Added methods `statistics`, `summary`, `headers` and `types` to `Response::ResultSet`
|
5
|
+
|
1
6
|
# 2.1.0
|
2
|
-
* `ClickHouse.connection.insert` now returns `ClickHouse::Response::
|
7
|
+
* `ClickHouse.connection.insert` now returns `ClickHouse::Response::Summary` object
|
3
8
|
with methods `headers`, `summary`, `written_rows`, `written_bytes`, etc...
|
4
9
|
* `ClickHouse.connection.insert(columns: ["id"], values: [1])` now uses `JSONCompactEachRow` by default
|
5
10
|
(to increase JSON serialization speed)
|
data/Gemfile.lock
CHANGED
data/Gemfile_faraday1.lock
CHANGED
data/Gemfile_faraday2.lock
CHANGED
data/README.md
CHANGED
@@ -138,12 +138,19 @@ Select all type-casted result set
|
|
138
138
|
@result = ClickHouse.connection.select_all('SELECT * FROM visits')
|
139
139
|
|
140
140
|
# all enumerable methods are delegated like #each, #map, #select etc
|
141
|
+
# results of #to_a is TYPE CASTED
|
141
142
|
@result.to_a #=> [{"date"=>#<Date: 2000-01-01>, "id"=>1}]
|
142
143
|
|
144
|
+
# raw results (WITHOUT type casting)
|
145
|
+
# much faster if selecting a large amount of data
|
146
|
+
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
|
147
|
+
|
143
148
|
# you can access raw data
|
144
149
|
@result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
|
145
|
-
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
|
146
150
|
@result.statistics #=> {"elapsed"=>0.0002271, "rows_read"=>2, "bytes_read"=>12}
|
151
|
+
@result.summary #=> ClickHouse::Response::Summary
|
152
|
+
@result.headers #=> {"x-clickhouse-query-id"=>"9bf5f604-31fc-4eff-a4b5-277f2c71d199"}
|
153
|
+
@result.types #=> [Hash<String|Symbol, ClickHouse::Ast::Statement>]
|
147
154
|
```
|
148
155
|
|
149
156
|
### Select Value
|
@@ -59,6 +59,17 @@ module ClickHouse
|
|
59
59
|
@arguments ||= []
|
60
60
|
end
|
61
61
|
|
62
|
+
# @return [Array]
|
63
|
+
# cached argument values to increase the casting perfomance
|
64
|
+
def argument_values
|
65
|
+
@argument_values ||= arguments.map(&:value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def argument_first!
|
69
|
+
# TODO: raise an error if multiple arguments
|
70
|
+
@argument_first ||= arguments.first
|
71
|
+
end
|
72
|
+
|
62
73
|
def placeholder
|
63
74
|
return @placeholder if defined?(@placeholder)
|
64
75
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'pry'
|
6
|
+
require_relative '../../click_house'
|
7
|
+
|
8
|
+
|
9
|
+
ClickHouse.config.json_serializer = ClickHouse::Serializer::JsonOjSerializer
|
10
|
+
ClickHouse.config.json_parser = ClickHouse::Middleware::ParseJsonOj
|
11
|
+
ClickHouse.connection.drop_table('benchmark', if_exists: true)
|
12
|
+
ClickHouse.connection.execute <<~SQL
|
13
|
+
CREATE TABLE benchmark(
|
14
|
+
int Nullable(Int8),
|
15
|
+
date Nullable(Date),
|
16
|
+
array Array(String),
|
17
|
+
map Map(String, IPv4)
|
18
|
+
) ENGINE Memory
|
19
|
+
SQL
|
20
|
+
|
21
|
+
INPUT = Array.new(200_000, {
|
22
|
+
'int' => 21341234,
|
23
|
+
'date' => Date.new(2022, 1, 1),
|
24
|
+
'array' => ['foo'],
|
25
|
+
'map' => {'ip' => IPAddr.new('127.0.0.1')}
|
26
|
+
})
|
27
|
+
|
28
|
+
Benchmark.bm do |x|
|
29
|
+
x.report('insert: no casting') do
|
30
|
+
ClickHouse.connection.insert('benchmark', INPUT)
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('insert: with casting') do
|
34
|
+
schema = ClickHouse.connection.table_schema('benchmark')
|
35
|
+
ClickHouse.connection.insert('benchmark', schema.serialize(INPUT))
|
36
|
+
end
|
37
|
+
|
38
|
+
x.report('select: no casting') do
|
39
|
+
ClickHouse.connection.select_all('SELECT * FROM benchmark').data
|
40
|
+
end
|
41
|
+
|
42
|
+
x.report('select: with casting') do
|
43
|
+
ClickHouse.connection.select_all('SELECT * FROM benchmark').to_a
|
44
|
+
end
|
45
|
+
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
|
@@ -46,7 +46,7 @@ module ClickHouse
|
|
46
46
|
return insert_compact(table, columns: columns, values: values, format: format)
|
47
47
|
end
|
48
48
|
|
49
|
-
Response::Factory.empty_exec
|
49
|
+
Response::Factory.empty_exec(config)
|
50
50
|
end
|
51
51
|
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
52
52
|
|
@@ -11,7 +11,16 @@ module ClickHouse
|
|
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)
|
@@ -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'
|
@@ -5,38 +5,46 @@ module ClickHouse
|
|
5
5
|
class Factory
|
6
6
|
KEY_META = 'meta'
|
7
7
|
KEY_DATA = 'data'
|
8
|
-
KEY_TOTALS = 'totals'
|
9
|
-
KEY_STATISTICS = 'statistics'
|
10
|
-
KEY_ROWS_BEFORE_LIMIT_AT_LEAST = 'rows_before_limit_at_least'
|
11
8
|
|
12
|
-
# @return [
|
9
|
+
# @return [ResultSet]
|
13
10
|
# @params faraday [Faraday::Response]
|
14
11
|
# @params config [Config]
|
15
12
|
def self.response(faraday, config)
|
16
13
|
body = faraday.body
|
17
14
|
|
18
|
-
|
19
|
-
|
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))
|
20
19
|
|
21
20
|
ResultSet.new(
|
22
21
|
config: config,
|
23
22
|
meta: body.fetch(config.key(KEY_META)),
|
24
23
|
data: body.fetch(config.key(KEY_DATA)),
|
25
|
-
|
26
|
-
statistics: body[config.key(KEY_STATISTICS)],
|
27
|
-
rows_before_limit_at_least: body[config.key(KEY_ROWS_BEFORE_LIMIT_AT_LEAST)]
|
24
|
+
summary: Middleware::SummaryMiddleware.extract(faraday.env)
|
28
25
|
)
|
29
26
|
end
|
30
27
|
|
31
|
-
# @return [
|
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)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Result of execution
|
39
|
+
# @return [Response::Summary]
|
32
40
|
# @params faraday [Faraday::Response]
|
33
41
|
def self.exec(faraday)
|
34
|
-
|
42
|
+
Middleware::SummaryMiddleware.extract(faraday.env)
|
35
43
|
end
|
36
44
|
|
37
|
-
# @return [Response::
|
38
|
-
def self.empty_exec
|
39
|
-
|
45
|
+
# @return [Response::Summary]
|
46
|
+
def self.empty_exec(config)
|
47
|
+
Summary.new(config)
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
@@ -13,24 +13,31 @@ module ClickHouse
|
|
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
|
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
|
17
29
|
|
18
30
|
# @param config [Config]
|
19
31
|
# @param meta [Array]
|
20
32
|
# @param data [Array]
|
21
|
-
# @param
|
22
|
-
|
23
|
-
# Hash in JSON format and Array in JSONCompact
|
24
|
-
# rubocop:disable Metrics/ParameterLists
|
25
|
-
def initialize(config:, 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)
|
26
35
|
@config = config
|
27
36
|
@meta = meta
|
28
37
|
@data = data
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@statistics = Hash(statistics)
|
38
|
+
@summary = summary
|
39
|
+
@to_a = to_a
|
32
40
|
end
|
33
|
-
# rubocop:enable Metrics/ParameterLists
|
34
41
|
|
35
42
|
# @return [Array, Hash]
|
36
43
|
# @param data [Array, Hash]
|
@@ -106,7 +113,7 @@ module ClickHouse
|
|
106
113
|
return cast_map(stmt, Hash(value)) if stmt.caster.map?
|
107
114
|
return cast_tuple(stmt, Array(value)) if stmt.caster.tuple?
|
108
115
|
|
109
|
-
stmt.caster.cast(value, *stmt.
|
116
|
+
stmt.caster.cast(value, *stmt.argument_values)
|
110
117
|
end
|
111
118
|
|
112
119
|
# @return [Hash]
|
@@ -124,8 +131,7 @@ module ClickHouse
|
|
124
131
|
# @param stmt [Ast::Statement]
|
125
132
|
def cast_container(stmt, value)
|
126
133
|
stmt.caster.cast_each(value) do |item|
|
127
|
-
|
128
|
-
cast_type(stmt.arguments.first, item)
|
134
|
+
cast_type(stmt.argument_first!, item)
|
129
135
|
end
|
130
136
|
end
|
131
137
|
|
@@ -142,14 +148,13 @@ module ClickHouse
|
|
142
148
|
return serialize_map(stmt, value) if stmt.caster.map?
|
143
149
|
return serialize_tuple(stmt, Array(value)) if stmt.caster.tuple?
|
144
150
|
|
145
|
-
stmt.caster.serialize(value, *stmt.
|
151
|
+
stmt.caster.serialize(value, *stmt.argument_values)
|
146
152
|
end
|
147
153
|
|
148
154
|
# @param stmt [Ast::Statement]
|
149
155
|
def serialize_container(stmt, value)
|
150
|
-
stmt.caster.
|
151
|
-
|
152
|
-
serialize_type(stmt.arguments.first, item)
|
156
|
+
stmt.caster.serialize_each(value) do |item|
|
157
|
+
serialize_type(stmt.argument_first!, item)
|
153
158
|
end
|
154
159
|
end
|
155
160
|
|
@@ -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
@@ -3,16 +3,37 @@
|
|
3
3
|
module ClickHouse
|
4
4
|
module Type
|
5
5
|
class DateTime64Type < BaseType
|
6
|
-
|
6
|
+
BASE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
7
|
+
CAST_FORMAT = "#{BASE_FORMAT}.%N"
|
8
|
+
SERIALIZE_FORMATS = {
|
9
|
+
0 => BASE_FORMAT,
|
10
|
+
1 => "#{BASE_FORMAT}.%1N",
|
11
|
+
2 => "#{BASE_FORMAT}.%2N",
|
12
|
+
3 => "#{BASE_FORMAT}.%3N",
|
13
|
+
4 => "#{BASE_FORMAT}.%4N",
|
14
|
+
5 => "#{BASE_FORMAT}.%5N",
|
15
|
+
6 => "#{BASE_FORMAT}.%6N",
|
16
|
+
7 => "#{BASE_FORMAT}.%7N",
|
17
|
+
8 => "#{BASE_FORMAT}.%8N",
|
18
|
+
9 => "#{BASE_FORMAT}.%9N",
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Tick size (precision):
|
22
|
+
# 10-precision seconds.
|
23
|
+
# Valid range: [ 0 : 9 ].
|
24
|
+
# Typically are used - 3 (milliseconds), 6 (microseconds), 9 (nanoseconds).
|
25
|
+
def cast(value, precision = 0, tz = nil)
|
26
|
+
format = precision.zero? ? BASE_FORMAT : CAST_FORMAT
|
27
|
+
|
7
28
|
if tz
|
8
|
-
Time.find_zone(tz).
|
29
|
+
Time.find_zone(tz).strptime(value, format)
|
9
30
|
else
|
10
|
-
Time.
|
31
|
+
Time.strptime(value, format)
|
11
32
|
end
|
12
33
|
end
|
13
34
|
|
14
35
|
def serialize(value, precision = 3, _tz = nil)
|
15
|
-
value.strftime(
|
36
|
+
value.strftime(SERIALIZE_FORMATS.fetch(precision))
|
16
37
|
end
|
17
38
|
end
|
18
39
|
end
|
@@ -3,16 +3,18 @@
|
|
3
3
|
module ClickHouse
|
4
4
|
module Type
|
5
5
|
class DateTimeType < BaseType
|
6
|
+
FORMAT = '%Y-%m-%d %H:%M:%S'
|
7
|
+
|
6
8
|
def cast(value, tz = nil)
|
7
9
|
if tz
|
8
|
-
Time.find_zone(tz).
|
10
|
+
Time.find_zone(tz).strptime(value, FORMAT)
|
9
11
|
else
|
10
|
-
Time.
|
12
|
+
Time.strptime(value, FORMAT)
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
def serialize(value, *)
|
15
|
-
value.strftime(
|
17
|
+
value.strftime(FORMAT)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
@@ -3,12 +3,14 @@
|
|
3
3
|
module ClickHouse
|
4
4
|
module Type
|
5
5
|
class DateType < BaseType
|
6
|
+
FORMAT = '%Y-%m-%d'
|
7
|
+
|
6
8
|
def cast(value)
|
7
|
-
Date.
|
9
|
+
Date.strptime(value, FORMAT)
|
8
10
|
end
|
9
11
|
|
10
12
|
def serialize(value)
|
11
|
-
value.strftime(
|
13
|
+
value.strftime(FORMAT)
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/click_house/util.rb
CHANGED
data/lib/click_house/version.rb
CHANGED
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.1.
|
4
|
+
version: 2.1.2
|
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-21 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/casting.rb
|
178
179
|
- lib/click_house/benchmark/map_join.rb
|
179
180
|
- lib/click_house/config.rb
|
180
181
|
- lib/click_house/connection.rb
|
@@ -200,10 +201,11 @@ files:
|
|
200
201
|
- lib/click_house/middleware/parse_json_oj.rb
|
201
202
|
- lib/click_house/middleware/raise_error.rb
|
202
203
|
- lib/click_house/middleware/response_base.rb
|
204
|
+
- lib/click_house/middleware/summary_middleware.rb
|
203
205
|
- lib/click_house/response.rb
|
204
|
-
- lib/click_house/response/execution.rb
|
205
206
|
- lib/click_house/response/factory.rb
|
206
207
|
- lib/click_house/response/result_set.rb
|
208
|
+
- lib/click_house/response/summary.rb
|
207
209
|
- lib/click_house/serializer.rb
|
208
210
|
- lib/click_house/serializer/base.rb
|
209
211
|
- lib/click_house/serializer/json_oj_serializer.rb
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ClickHouse
|
4
|
-
module Response
|
5
|
-
class Execution
|
6
|
-
SUMMARY_HEADER = 'x-clickhouse-summary'
|
7
|
-
|
8
|
-
attr_reader :headers, :summary
|
9
|
-
|
10
|
-
# @param headers [Faraday::Utils::Headers]
|
11
|
-
def initialize(headers: Faraday::Utils::Headers.new)
|
12
|
-
@headers = headers
|
13
|
-
@summary = parse_summary(headers[SUMMARY_HEADER])
|
14
|
-
end
|
15
|
-
|
16
|
-
# @return [Integer]
|
17
|
-
def read_rows
|
18
|
-
summary['read_rows'].to_i
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [Integer]
|
22
|
-
def read_bytes
|
23
|
-
summary['read_bytes'].to_i
|
24
|
-
end
|
25
|
-
|
26
|
-
# @return [Integer]
|
27
|
-
def written_rows
|
28
|
-
summary['written_rows'].to_i
|
29
|
-
end
|
30
|
-
|
31
|
-
# @return [Integer]
|
32
|
-
def written_bytes
|
33
|
-
summary['written_bytes'].to_i
|
34
|
-
end
|
35
|
-
|
36
|
-
# @return [Integer]
|
37
|
-
def total_rows_to_read
|
38
|
-
summary['total_rows_to_read'].to_i
|
39
|
-
end
|
40
|
-
|
41
|
-
# @return [Integer]
|
42
|
-
def result_rows
|
43
|
-
summary['result_rows'].to_i
|
44
|
-
end
|
45
|
-
|
46
|
-
# @return [Integer]
|
47
|
-
def result_bytes
|
48
|
-
summary['result_bytes'].to_i
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
# @return [Hash]
|
54
|
-
# {
|
55
|
-
# "read_rows" => "1",
|
56
|
-
# "read_bytes" => "23",
|
57
|
-
# "written_rows" => "1",
|
58
|
-
# "written_bytes" => "23",
|
59
|
-
# "total_rows_to_read" => "0",
|
60
|
-
# "result_rows" => "1",
|
61
|
-
# "result_bytes" => "23",
|
62
|
-
# }
|
63
|
-
def parse_summary(value)
|
64
|
-
return {} if value.nil? || value.empty?
|
65
|
-
|
66
|
-
JSON.parse(value)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|