click_house 2.1.0 → 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 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