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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f271ed936f2d0f66ec2045ec72025e317523dc8d70123c2bcf145ede5b4f1871
4
- data.tar.gz: d3e48437b08cbf5eb96315bd46ba88429302bcbebc882d787257c3636c586c8f
3
+ metadata.gz: f311abaedb0ef60cbfc25cabaae1223d266b9e5b83f677b1267c5e8b1b56048e
4
+ data.tar.gz: 58696f010c09d829038670e703126d28be343471b45fed0c07a05727d7ba0e95
5
5
  SHA512:
6
- metadata.gz: 3bae70c6d33a93ad298db0d14b91beb4bbfe31117cb51e9a58cdeeb95dc48e506363d3e660e6b9e3cd54398df2065210f3b0582e1891bb76e2bdd900520269b3
7
- data.tar.gz: cb202cf091808068383a32bb3a2a078aaacc3e051e5c30ecfc47d0bdfab84bb188f0ace4dbc6dbd2950407e5743deeb5f2ffd82c3b31a27ef6fcea01a3fdab1d
6
+ metadata.gz: 35b8b80d3beca3ed584f29dd532d7a4e525dbc355d1cca5bca3d21d32f294ed6ba95b24ef01a6d4930ef195cbe9ab98ee0517a9620ffb1aa45c06353ddf557f1
7
+ data.tar.gz: 4ba73ab43c95300c19994e90b2ee7cade877490655ec07962c1dae1175968e36eb51a5711fe2ea8da49a934acacf6414d02052e9f6594914c591be8d6cac43fe
data/.rubocop.yml CHANGED
@@ -7,6 +7,7 @@ AllCops:
7
7
  Exclude:
8
8
  - 'click_house.gemspec'
9
9
  - 'bin/*'
10
+ - 'lib/click_house/benchmark/*'
10
11
  - 'spec/**/*'
11
12
  - 'vendor/**/*'
12
13
  TargetRubyVersion: 2.7
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::Execution` objects
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- click_house (2.1.0)
4
+ click_house (2.1.2)
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.2)
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.2)
5
5
  activesupport
6
6
  faraday (>= 1.7, < 3)
7
7
 
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 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]
@@ -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.arguments.map(&:value))
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
- # TODO: raise an error if multiple arguments
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.arguments.map(&:value))
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.cast_each(value) do |item|
151
- # TODO: raise an error if multiple arguments
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
@@ -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
@@ -3,16 +3,37 @@
3
3
  module ClickHouse
4
4
  module Type
5
5
  class DateTime64Type < BaseType
6
- def cast(value, _precision = nil, tz = nil)
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).parse(value)
29
+ Time.find_zone(tz).strptime(value, format)
9
30
  else
10
- Time.parse(value)
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("%Y-%m-%d %H:%M:%S.%#{precision}N")
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).parse(value)
10
+ Time.find_zone(tz).strptime(value, FORMAT)
9
11
  else
10
- Time.parse(value)
12
+ Time.strptime(value, FORMAT)
11
13
  end
12
14
  end
13
15
 
14
16
  def serialize(value, *)
15
- value.strftime('%Y-%m-%d %H:%M:%S')
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.parse(value)
9
+ Date.strptime(value, FORMAT)
8
10
  end
9
11
 
10
12
  def serialize(value)
11
- value.strftime('%Y-%m-%d')
13
+ value.strftime(FORMAT)
12
14
  end
13
15
  end
14
16
  end
@@ -4,11 +4,11 @@ module ClickHouse
4
4
  module Type
5
5
  class IntegerType < BaseType
6
6
  def cast(value)
7
- Integer(value) unless value.nil?
7
+ Integer(value)
8
8
  end
9
9
 
10
10
  def serialize(value)
11
- value.to_i unless value.nil?
11
+ value.to_i
12
12
  end
13
13
  end
14
14
  end
@@ -4,11 +4,11 @@ module ClickHouse
4
4
  module Type
5
5
  class IPType < BaseType
6
6
  def cast(value)
7
- IPAddr.new(value) unless value.nil?
7
+ IPAddr.new(value)
8
8
  end
9
9
 
10
10
  def serialize(value)
11
- value.to_s unless value.nil?
11
+ value.to_s
12
12
  end
13
13
  end
14
14
  end
@@ -4,7 +4,11 @@ module ClickHouse
4
4
  module Type
5
5
  class LowCardinalityType < BaseType
6
6
  def cast_each(value, *_argv)
7
- yield(value) unless value.nil?
7
+ yield(value)
8
+ end
9
+
10
+ def serialize_each(value, *_argv)
11
+ yield(value)
8
12
  end
9
13
 
10
14
  def container?
@@ -7,6 +7,10 @@ module ClickHouse
7
7
  yield(value) unless value.nil?
8
8
  end
9
9
 
10
+ def serialize_each(value, *_argv)
11
+ yield(value) unless value.nil?
12
+ end
13
+
10
14
  def container?
11
15
  true
12
16
  end
@@ -4,11 +4,11 @@ module ClickHouse
4
4
  module Type
5
5
  class StringType < BaseType
6
6
  def cast(value, *)
7
- value.to_s unless value.nil?
7
+ value.to_s
8
8
  end
9
9
 
10
10
  def serialize(value, *)
11
- value.to_s unless value.nil?
11
+ value.to_s
12
12
  end
13
13
  end
14
14
  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.2'
5
5
  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.1.0
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-20 00:00:00.000000000 Z
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