click_house 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f271ed936f2d0f66ec2045ec72025e317523dc8d70123c2bcf145ede5b4f1871
4
- data.tar.gz: d3e48437b08cbf5eb96315bd46ba88429302bcbebc882d787257c3636c586c8f
3
+ metadata.gz: 364117bb31fd3478fcf00d981b61205aa9fb864abbfeb9f9bdf253e53a0917fa
4
+ data.tar.gz: d0b0d7a21175a9c2e044a88351ddd6d107b1875769ae737d3e547e41e01d93d1
5
5
  SHA512:
6
- metadata.gz: 3bae70c6d33a93ad298db0d14b91beb4bbfe31117cb51e9a58cdeeb95dc48e506363d3e660e6b9e3cd54398df2065210f3b0582e1891bb76e2bdd900520269b3
7
- data.tar.gz: cb202cf091808068383a32bb3a2a078aaacc3e051e5c30ecfc47d0bdfab84bb188f0ace4dbc6dbd2950407e5743deeb5f2ffd82c3b31a27ef6fcea01a3fdab1d
6
+ metadata.gz: 481ba8166f2300302c1a0836e336097e97a25015fbe5a5cf8e8fb5912b2170d50a2a1bc25178deb8b4a4b17d63e5638d0200bbca5a84c6a237de694ecf5f4d76
7
+ data.tar.gz: ba051b743929517452507d54f98de6d57de724b4400d1cf9db02666a48c5b00d4dbd00a3ffab6b5f2f9c3a7db78c15f84ae36523e2ee76946526fe131722ee05
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
+ # 2.1.1
2
+ * Fix logging with symbolized keys JSON
3
+ * Unknown formats return raw `Response::ResultSelt` like regular JSON query
4
+
1
5
  # 2.1.0
2
- * `ClickHouse.connection.insert` now returns `ClickHouse::Response::Execution` objects
6
+ * `ClickHouse.connection.insert` now returns `ClickHouse::Response::Summary` object
3
7
  with methods `headers`, `summary`, `written_rows`, `written_bytes`, etc...
4
8
  * `ClickHouse.connection.insert(columns: ["id"], values: [1])` now uses `JSONCompactEachRow` by default
5
9
  (to increase JSON serialization speed)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.1.0)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.1.0)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.1.0)
4
+ click_house (2.1.1)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
data/README.md CHANGED
@@ -138,12 +138,16 @@ 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
 
143
144
  # you can access raw data
144
145
  @result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
145
146
  @result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
146
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>]
147
151
  ```
148
152
 
149
153
  ### Select Value
@@ -65,8 +65,9 @@ module ClickHouse
65
65
 
66
66
  conn.response Middleware::RaiseError
67
67
  conn.response Middleware::Logging, logger: config.logger!
68
- conn.response config.json_parser, content_type: %r{application/json}, parser_options: { config: config }
69
- conn.response Middleware::ParseCsv, content_type: %r{text/csv}, parser_options: { config: config }
68
+ conn.response Middleware::SummaryMiddleware, options: { config: config } # should be after logger
69
+ conn.response config.json_parser, content_type: %r{application/json}, options: { config: config }
70
+ conn.response Middleware::ParseCsv, content_type: %r{text/csv}, options: { config: config }
70
71
  conn.adapter config.adapter
71
72
  end
72
73
  end
@@ -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
- Array(Response::Factory.response(response, config).first).dig(0, -1)
14
+ got = Response::Factory.response(response, config).first
15
+
16
+ case got
17
+ when Hash
18
+ Array(got).dig(0, -1) # get a value of a first key for JSON format
19
+ when Array
20
+ got[0] # for CSV format
21
+ else
22
+ got # for RowBinary format
23
+ end
15
24
  end
16
25
 
17
26
  def select_one(sql)
@@ -5,36 +5,32 @@ module ClickHouse
5
5
  class Logging < Faraday::Middleware
6
6
  Faraday::Response.register_middleware self => self
7
7
 
8
- SUMMARY_HEADER = 'x-clickhouse-summary'
8
+ EMPTY = ''
9
+ GET = :get
9
10
 
10
- attr_reader :logger, :starting, :body
11
+ attr_reader :logger, :starting
11
12
 
12
13
  def initialize(app = nil, logger:)
13
14
  @logger = logger
14
15
  super(app)
15
16
  end
16
17
 
17
- def call(environment)
18
+ def call(env)
18
19
  @starting = timestamp
19
- @body = environment.body if log_body?
20
- @app.call(environment).on_complete(&method(:on_complete))
21
- end
22
-
23
- private
24
-
25
- def log_body?
26
- logger.level == Logger::DEBUG
20
+ super
27
21
  end
28
22
 
29
23
  # rubocop:disable Layout/LineLength
30
24
  def on_complete(env)
31
- summary = extract_summary(env.response_headers)
32
- logger.info("\e[1mSQL (#{duration_stats_log(env.body)})\e[0m #{query(env)};")
33
- logger.debug(body) if body
34
- logger.info("\e[1mRead: #{summary.fetch(:read_rows)} rows, #{summary.fetch(:read_bytes)}. Written: #{summary.fetch(:written_rows)} rows, #{summary.fetch(:written_bytes)}\e[0m")
25
+ summary = SummaryMiddleware.extract(env)
26
+ logger.info("\e[1mSQL (#{duration_stats_log(summary)})\e[0m #{query(env)};")
27
+ logger.debug(env.request_body) if log_body?(env)
28
+ logger.info("\e[1mRead: #{summary.read_rows} rows, #{summary.read_bytes_pretty}. Written: #{summary.written_rows} rows, #{summary.written_bytes_pretty}\e[0m")
35
29
  end
36
30
  # rubocop:enable Layout/LineLength
37
31
 
32
+ private
33
+
38
34
  def duration
39
35
  timestamp - starting
40
36
  end
@@ -43,37 +39,25 @@ module ClickHouse
43
39
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
40
  end
45
41
 
46
- def query_in_body?(env)
47
- env.method == :get
42
+ # @return [Boolean]
43
+ def log_body?(env)
44
+ return unless logger.debug?
45
+ return if env.method == GET # GET queries logs body as a statement
46
+ return if env.request_body.nil? || env.request_body == EMPTY
47
+
48
+ true
48
49
  end
49
50
 
50
51
  def query(env)
51
- if query_in_body?(env)
52
- body
52
+ if env.method == GET
53
+ env.request_body
53
54
  else
54
- CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]'
55
+ String(CGI.parse(env.url.query.to_s).dig('query', 0) || '[NO QUERY]').chomp
55
56
  end
56
57
  end
57
58
 
58
- def duration_stats_log(body)
59
- elapsed = duration
60
- clickhouse_elapsed = body['statistics'].fetch('elapsed') if body.is_a?(Hash) && body.key?('statistics')
61
-
62
- [
63
- "Total: #{Util::Pretty.measure(elapsed * 1000)}",
64
- ("CH: #{Util::Pretty.measure(clickhouse_elapsed * 1000)}" if clickhouse_elapsed)
65
- ].compact.join(', ')
66
- end
67
-
68
- def extract_summary(headers)
69
- JSON.parse(headers.fetch('x-clickhouse-summary', '{}')).tap do |summary|
70
- summary[:read_rows] = summary['read_rows']
71
- summary[:read_bytes] = Util::Pretty.size(summary['read_bytes'].to_i)
72
- summary[:written_rows] = summary['written_rows']
73
- summary[:written_bytes] = Util::Pretty.size(summary['written_bytes'].to_i)
74
- end
75
- rescue JSON::ParserError
76
- {}
59
+ def duration_stats_log(summary)
60
+ "Total: #{Util::Pretty.measure(duration * 1000)}, CH: #{summary.elapsed_pretty}"
77
61
  end
78
62
  end
79
63
  end
@@ -5,12 +5,12 @@ module ClickHouse
5
5
  class ResponseBase < Faraday::Middleware
6
6
  CONTENT_TYPE_HEADER = 'content-type'
7
7
 
8
- attr_reader :parser_options
8
+ attr_reader :options
9
9
  attr_reader :content_type
10
10
 
11
- def initialize(app = nil, parser_options: {}, content_type: nil, preserve_raw: false)
11
+ def initialize(app = nil, options: {}, content_type: nil, preserve_raw: false)
12
12
  super(app)
13
- @parser_options = parser_options
13
+ @options = options
14
14
  @content_type = content_type
15
15
  @preserve_raw = preserve_raw
16
16
  on_setup
@@ -32,7 +32,7 @@ module ClickHouse
32
32
 
33
33
  # @return [Config]
34
34
  def config
35
- parser_options.fetch(:config)
35
+ options.fetch(:config)
36
36
  end
37
37
 
38
38
  private
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Middleware
5
+ class SummaryMiddleware < ResponseBase
6
+ Faraday::Response.register_middleware self => self
7
+
8
+ KEY = :summary
9
+
10
+ # @param env [Faraday::Env]
11
+ # @return [Response::Summary]
12
+ def self.extract(env)
13
+ env.custom_members.fetch(KEY)
14
+ end
15
+
16
+ # @param env [Faraday::Env]
17
+ def on_complete(env)
18
+ env.custom_members[KEY] = Response::Summary.new(
19
+ config,
20
+ headers: env.response_headers,
21
+ body: env.body.is_a?(Hash) ? env.body : {}
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,6 +3,7 @@
3
3
  module ClickHouse
4
4
  module Middleware
5
5
  autoload :ResponseBase, 'click_house/middleware/response_base'
6
+ autoload :SummaryMiddleware, 'click_house/middleware/summary_middleware'
6
7
  autoload :Logging, 'click_house/middleware/logging'
7
8
  autoload :ParseCsv, 'click_house/middleware/parse_csv'
8
9
  autoload :ParseJsonOj, 'click_house/middleware/parse_json_oj'
@@ -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 [Nil], ResultSet]
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
- return body unless body.is_a?(Hash)
19
- return body unless body.key?(config.key(KEY_META)) && body.key?(config.key(KEY_DATA))
15
+ # wrap to be able to use connection#select_one, connection#select_value
16
+ # with other formats like binary
17
+ return raw(faraday, config) unless body.is_a?(Hash)
18
+ return raw(faraday, config) unless body.key?(config.key(KEY_META)) && body.key?(config.key(KEY_DATA))
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
- totals: body[config.key(KEY_TOTALS)],
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 [Response::Execution]
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
- Execution.new(headers: faraday.headers)
42
+ Middleware::SummaryMiddleware.extract(faraday.env)
35
43
  end
36
44
 
37
- # @return [Response::Execution]
38
- def self.empty_exec
39
- Execution.new
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
- attr_reader :config, :meta, :data, :totals, :statistics, :rows_before_limit_at_least
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 totals [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
22
- # https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
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
- @totals = totals
30
- @rows_before_limit_at_least = rows_before_limit_at_least
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]
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Response
5
+ class Summary
6
+ SUMMARY_HEADER = 'x-clickhouse-summary'
7
+ KEY_TOTALS = 'totals'
8
+ KEY_STATISTICS = 'statistics'
9
+ KEY_ROWS_BEFORE_LIMIT_AT_LEAST = 'rows_before_limit_at_least'
10
+ KEY_STAT_ELAPSED = 'elapsed'
11
+
12
+ attr_reader :config,
13
+ :headers,
14
+ :summary,
15
+ # {:elapsed=>0.387287e-3, :rows_read=>0, :bytes_read=>0}}
16
+ :statistics,
17
+ :totals,
18
+ :rows_before_limit_at_least
19
+
20
+ # @param config [Config]
21
+ # @param headers [Faraday::Utils::Headers]
22
+ # @param body [Hash]
23
+ # TOTALS [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
24
+ # https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/#with-totals-modifier
25
+ # Hash in JSON format and Array in JSONCompact
26
+ def initialize(config, headers: Faraday::Utils::Headers.new, body: {})
27
+ @headers = headers
28
+ @config = config
29
+ @statistics = body.fetch(config.key(KEY_STATISTICS), {})
30
+ @totals = body[config.key(KEY_TOTALS)]
31
+ @rows_before_limit_at_least = body[config.key(KEY_ROWS_BEFORE_LIMIT_AT_LEAST)]
32
+ @summary = parse_summary(headers[SUMMARY_HEADER])
33
+ end
34
+
35
+ # @return [Integer]
36
+ def read_rows
37
+ summary[config.key('read_rows')].to_i
38
+ end
39
+
40
+ # @return [Integer]
41
+ def read_bytes
42
+ summary[config.key('read_bytes')].to_i
43
+ end
44
+
45
+ # @return [String]
46
+ def read_bytes_pretty
47
+ Util::Pretty.size(read_bytes)
48
+ end
49
+
50
+ # @return [Integer]
51
+ def written_rows
52
+ summary[config.key('written_rows')].to_i
53
+ end
54
+
55
+ # @return [Integer]
56
+ def written_bytes
57
+ summary[config.key('written_bytes')].to_i
58
+ end
59
+
60
+ # @return [String]
61
+ def written_bytes_pretty
62
+ Util::Pretty.size(written_bytes)
63
+ end
64
+
65
+ # @return [Integer]
66
+ def total_rows_to_read
67
+ summary[config.key('total_rows_to_read')].to_i
68
+ end
69
+
70
+ # @return [Integer]
71
+ def result_rows
72
+ summary[config.key('result_rows')].to_i
73
+ end
74
+
75
+ # @return [Integer]
76
+ def result_bytes
77
+ summary[config.key('result_bytes')].to_i
78
+ end
79
+
80
+ # @return [Float]
81
+ def elapsed
82
+ statistics[config.key(KEY_STAT_ELAPSED)].to_f
83
+ end
84
+
85
+ # @return [String]
86
+ def elapsed_pretty
87
+ Util::Pretty.measure(elapsed * 1000)
88
+ end
89
+
90
+ private
91
+
92
+ # @return [Hash]
93
+ # {
94
+ # "read_rows" => "1",
95
+ # "read_bytes" => "23",
96
+ # "written_rows" => "1",
97
+ # "written_bytes" => "23",
98
+ # "total_rows_to_read" => "0",
99
+ # "result_rows" => "1",
100
+ # "result_bytes" => "23",
101
+ # }
102
+ def parse_summary(value)
103
+ return {} if value.nil? || value.empty?
104
+
105
+ JSON.parse(value)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -4,6 +4,6 @@ module ClickHouse
4
4
  module Response
5
5
  autoload :Factory, 'click_house/response/factory'
6
6
  autoload :ResultSet, 'click_house/response/result_set'
7
- autoload :Execution, 'click_house/response/execution'
7
+ autoload :Summary, 'click_house/response/summary'
8
8
  end
9
9
  end
@@ -4,5 +4,12 @@ module ClickHouse
4
4
  module Util
5
5
  autoload :Statement, 'click_house/util/statement'
6
6
  autoload :Pretty, 'click_house/util/pretty'
7
+
8
+ module_function
9
+
10
+ # wraps
11
+ def array(input)
12
+ input.is_a?(Array) ? input : [input]
13
+ end
7
14
  end
8
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClickHouse
4
- VERSION = '2.1.0'
4
+ VERSION = '2.1.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: click_house
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aliaksandr Shylau
@@ -200,10 +200,11 @@ files:
200
200
  - lib/click_house/middleware/parse_json_oj.rb
201
201
  - lib/click_house/middleware/raise_error.rb
202
202
  - lib/click_house/middleware/response_base.rb
203
+ - lib/click_house/middleware/summary_middleware.rb
203
204
  - lib/click_house/response.rb
204
- - lib/click_house/response/execution.rb
205
205
  - lib/click_house/response/factory.rb
206
206
  - lib/click_house/response/result_set.rb
207
+ - lib/click_house/response/summary.rb
207
208
  - lib/click_house/serializer.rb
208
209
  - lib/click_house/serializer/base.rb
209
210
  - 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