click_house 1.6.2 → 2.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +16 -5
  3. data/.rubocop.yml +77 -1
  4. data/CHANGELOG.md +15 -0
  5. data/Gemfile.lock +41 -48
  6. data/Gemfile_faraday1 +11 -0
  7. data/Gemfile_faraday1.lock +111 -0
  8. data/Gemfile_faraday2 +1 -0
  9. data/Gemfile_faraday2.lock +88 -0
  10. data/Makefile +25 -0
  11. data/README.md +6 -20
  12. data/click_house.gemspec +4 -3
  13. data/docker-compose.yml +1 -1
  14. data/lib/click_house/ast/parser.rb +53 -0
  15. data/lib/click_house/ast/statement.rb +99 -0
  16. data/lib/click_house/ast/ticker.rb +42 -0
  17. data/lib/click_house/ast.rb +9 -0
  18. data/lib/click_house/config.rb +17 -1
  19. data/lib/click_house/connection.rb +3 -2
  20. data/lib/click_house/definition/column.rb +6 -3
  21. data/lib/click_house/definition/column_set.rb +3 -2
  22. data/lib/click_house/extend/configurable.rb +2 -0
  23. data/lib/click_house/extend/connection_inserting.rb +3 -3
  24. data/lib/click_house/extend/type_definition.rb +1 -10
  25. data/lib/click_house/middleware/logging.rb +1 -1
  26. data/lib/click_house/middleware/parse_csv.rb +5 -6
  27. data/lib/click_house/middleware/parse_json.rb +16 -0
  28. data/lib/click_house/middleware/parse_json_oj.rb +22 -0
  29. data/lib/click_house/middleware/raise_error.rb +8 -8
  30. data/lib/click_house/middleware/response_base.rb +45 -0
  31. data/lib/click_house/middleware.rb +3 -0
  32. data/lib/click_house/response/result_set.rb +58 -40
  33. data/lib/click_house/response.rb +1 -0
  34. data/lib/click_house/type/array_type.rb +6 -12
  35. data/lib/click_house/type/base_type.rb +25 -1
  36. data/lib/click_house/type/date_time64_type.rb +5 -1
  37. data/lib/click_house/type/date_time_type.rb +5 -1
  38. data/lib/click_house/type/decimal_type.rb +15 -1
  39. data/lib/click_house/type/low_cardinality_type.rb +15 -0
  40. data/lib/click_house/type/map_type.rb +15 -0
  41. data/lib/click_house/type/nullable_type.rb +6 -8
  42. data/lib/click_house/type/string_type.rb +1 -1
  43. data/lib/click_house/type/tuple_type.rb +15 -0
  44. data/lib/click_house/type.rb +3 -0
  45. data/lib/click_house/version.rb +1 -1
  46. data/lib/click_house.rb +10 -3
  47. metadata +38 -4
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module ClickHouse
6
+ module Ast
7
+ class Parser
8
+ OPEN = '('
9
+ CLOSED = ')'
10
+ COMMA = ','
11
+ SPACE = ' '
12
+
13
+ attr_reader :input
14
+
15
+ # @param input [String]
16
+ def initialize(input)
17
+ @input = input
18
+ end
19
+
20
+ # @refs https://clickhouse.com/docs/en/sql-reference/data-types/
21
+ # Map(String, Decimal(10, 5))
22
+ # Array(Array(Array(Array(Nullable(Int, String)))))
23
+ def parse
24
+ ticker = Ticker.new
25
+ control = false
26
+
27
+ input.each_char do |char|
28
+ # cases like (1,<space after comma> 3)
29
+ next if control && char == SPACE
30
+
31
+ case char
32
+ when OPEN
33
+ control = true
34
+ ticker.open
35
+ when CLOSED
36
+ control = true
37
+ ticker.close
38
+ when COMMA
39
+ control = true
40
+ ticker.comma
41
+ else
42
+ control = false
43
+ ticker.char(char)
44
+ end
45
+ end
46
+
47
+ # if a single type like "Int"
48
+ ticker.current.name! unless control
49
+ ticker.current
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module ClickHouse
6
+ module Ast
7
+ class Statement
8
+ PLACEHOLDER_S = '%s'
9
+ PLACEHOLDER_D = '%d'
10
+ DIGIT_RE = /\A\d+\Z/.freeze
11
+
12
+ attr_reader :name
13
+ attr_accessor :caster
14
+
15
+ def initialize(name: '')
16
+ @buffer = ''
17
+ @name = name
18
+ @opened = false
19
+ end
20
+
21
+ # @param value [String]
22
+ def print(value)
23
+ @buffer = "#{@buffer}#{value}"
24
+ end
25
+
26
+ def name!
27
+ @name = @buffer
28
+ @buffer = ''
29
+ end
30
+
31
+ def argument!
32
+ add_argument(Statement.new(name: @buffer))
33
+ @buffer = ''
34
+ end
35
+
36
+ # @param st [Statement]
37
+ def add_argument(st)
38
+ arguments.push(st)
39
+ end
40
+
41
+ # @param other [Statement]
42
+ def merge(other)
43
+ if other.named?
44
+ add_argument(other)
45
+ else
46
+ @arguments = arguments.concat(other.arguments)
47
+ end
48
+ end
49
+
50
+ def named?
51
+ !@name.empty?
52
+ end
53
+
54
+ def buffer?
55
+ !@buffer.empty?
56
+ end
57
+
58
+ # @return [Array<Statement>]
59
+ def arguments
60
+ @arguments ||= []
61
+ end
62
+
63
+ def placeholder
64
+ return @placeholder if defined?(@placeholder)
65
+
66
+ @placeholder = digit? ? PLACEHOLDER_D : PLACEHOLDER_S
67
+ end
68
+
69
+ def digit?
70
+ name.match?(DIGIT_RE)
71
+ end
72
+
73
+ def value
74
+ @value ||=
75
+ case placeholder
76
+ when PLACEHOLDER_D
77
+ Integer(name)
78
+ when PLACEHOLDER_S
79
+ # remove leading and trailing quotes
80
+ name[1..-2]
81
+ else
82
+ raise "unknown value extractor for <#{placeholder}>"
83
+ end
84
+ end
85
+
86
+ def to_s
87
+ out = StringIO.new
88
+ out.print(name.empty? ? 'NO_NAME' : name)
89
+ out.print("<#{@buffer}>") unless @buffer.empty?
90
+
91
+ if arguments.any?
92
+ out.print("(#{arguments.join(',')})")
93
+ end
94
+
95
+ out.string
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module ClickHouse
6
+ module Ast
7
+ class Ticker
8
+ attr_reader :root, :current
9
+
10
+ def initialize
11
+ @current = Statement.new
12
+ end
13
+
14
+ def open
15
+ current.name!
16
+ opened.push(current)
17
+ @current = Statement.new
18
+ end
19
+
20
+ def comma
21
+ current.argument! if current.buffer?
22
+ opened.last.merge(current)
23
+ @current = Statement.new
24
+ end
25
+
26
+ def close
27
+ current.argument! unless current.named?
28
+ opened.last.merge(current)
29
+ @current = opened.pop
30
+ end
31
+
32
+ # @param char [String]
33
+ def char(char)
34
+ current.print(char)
35
+ end
36
+
37
+ def opened
38
+ @opened ||= []
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Ast
5
+ autoload :Statement, 'click_house/ast/statement'
6
+ autoload :Ticker, 'click_house/ast/ticker'
7
+ autoload :Parser, 'click_house/ast/parser'
8
+ end
9
+ end
@@ -16,7 +16,20 @@ module ClickHouse
16
16
  open_timeout: nil,
17
17
  ssl_verify: false,
18
18
  headers: {},
19
- global_params: {}
19
+ global_params: {},
20
+ json_parser: ClickHouse::Middleware::ParseJson,
21
+ oj_load_options: {
22
+ mode: :custom,
23
+ allow_blank: true,
24
+ bigdecimal_as_decimal: false, # dump BigDecimal as a String
25
+ bigdecimal_load: :bigdecimal, # convert all decimal numbers to BigDecimal
26
+ empty_string: false,
27
+ second_precision: 6,
28
+ time_format: :ruby,
29
+ },
30
+ json_load_options: {
31
+ decimal_class: BigDecimal,
32
+ },
20
33
  }.freeze
21
34
 
22
35
  attr_accessor :adapter
@@ -33,6 +46,9 @@ module ClickHouse
33
46
  attr_accessor :ssl_verify
34
47
  attr_accessor :headers
35
48
  attr_accessor :global_params
49
+ attr_accessor :oj_load_options
50
+ attr_accessor :json_load_options
51
+ attr_accessor :json_parser # response middleware
36
52
 
37
53
  def initialize(params = {})
38
54
  assign(DEFAULTS.merge(params))
@@ -46,6 +46,7 @@ module ClickHouse
46
46
  transport.post(compose('/', query.merge(database: database, **params)), body)
47
47
  end
48
48
 
49
+ # transport should work the same both with Faraday v1 and Faraday v2
49
50
  def transport
50
51
  @transport ||= Faraday.new(config.url!) do |conn|
51
52
  conn.options.timeout = config.timeout
@@ -55,8 +56,8 @@ module ClickHouse
55
56
  conn.request(:basic_auth, config.username, config.password) if config.auth?
56
57
  conn.response Middleware::RaiseError
57
58
  conn.response Middleware::Logging, logger: config.logger!
58
- conn.response :json, content_type: %r{application/json}
59
- conn.response Middleware::ParseCsv, content_type: %r{text/csv}
59
+ conn.response config.json_parser, content_type: %r{application/json}, parser_options: { config: config }
60
+ conn.response Middleware::ParseCsv, content_type: %r{text/csv}, parser_options: { config: config }
60
61
  conn.adapter config.adapter
61
62
  end
62
63
  end
@@ -6,6 +6,7 @@ module ClickHouse
6
6
  attr_accessor :name
7
7
  attr_accessor :type
8
8
  attr_accessor :nullable
9
+ attr_accessor :low_cardinality
9
10
  attr_accessor :extensions
10
11
  attr_accessor :default
11
12
  attr_accessor :materialized
@@ -17,10 +18,12 @@ module ClickHouse
17
18
  end
18
19
 
19
20
  def to_s
20
- nullable ? "#{name} Nullable(#{extension_type}) #{opts}" : "#{name} #{extension_type} #{opts}"
21
- end
21
+ type = extension_type
22
+ type = "Nullable(#{type})" if nullable
23
+ type = "LowCardinality(#{type})" if low_cardinality
22
24
 
23
- private
25
+ "#{name} #{type} #{opts}"
26
+ end
24
27
 
25
28
  def opts
26
29
  options = {
@@ -3,7 +3,9 @@
3
3
  module ClickHouse
4
4
  module Definition
5
5
  class ColumnSet
6
- TYPES = ClickHouse.type_names(nullable: false).map { |s| s.sub('%s', "'%s'") }.freeze
6
+ TYPES = ClickHouse.types.each_with_object([]) do |(name, type), object|
7
+ object << name.sub('%s', "'%s'") if type.ddl?
8
+ end
7
9
 
8
10
  class << self
9
11
  # @input "DateTime('%s')"
@@ -15,7 +17,6 @@ module ClickHouse
15
17
 
16
18
  TYPES.each do |type|
17
19
  method_name = method_name_for_type(type)
18
-
19
20
  # t.Decimal :customer_id, nullable: true, default: ''
20
21
  # t.Decimal :money, 1, 2, nullable: true, default: ''
21
22
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
@@ -4,6 +4,8 @@ module ClickHouse
4
4
  module Extend
5
5
  module Configurable
6
6
  def config(&block)
7
+ yield(@config) if defined?(@config) && block
8
+
7
9
  @config ||= Config.new(&block)
8
10
  end
9
11
  end
@@ -14,8 +14,8 @@ module ClickHouse
14
14
  # end
15
15
  #
16
16
  # == Example with a param
17
- # subject.insert('rspec', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }])
18
- def insert(table, columns: [], values: [])
17
+ # subject.insert('rspec', values: [{ name: 'Sun', id: 1 }, { name: 'Moon', id: 2 }], format: 'JSONStringsEachRow')
18
+ def insert(table, columns: [], values: [], format: 'JSONEachRow')
19
19
  yield(values) if block_given?
20
20
 
21
21
  body = if columns.empty?
@@ -26,7 +26,7 @@ module ClickHouse
26
26
 
27
27
  return EMPTY_INSERT if values.empty?
28
28
 
29
- execute("INSERT INTO #{table} FORMAT JSONEachRow", body.join("\n")).success?
29
+ execute("INSERT INTO #{table} FORMAT #{format}", body.join("\n")).success?
30
30
  end
31
31
  end
32
32
  end
@@ -3,21 +3,12 @@
3
3
  module ClickHouse
4
4
  module Extend
5
5
  module TypeDefinition
6
- NULLABLE = 'Nullable'
7
- NULLABLE_RE = /#{NULLABLE}/i.freeze
8
-
9
6
  def types
10
7
  @types ||= Hash.new(Type::UndefinedType.new)
11
8
  end
12
9
 
13
- def add_type(type, klass, nullable: true)
10
+ def add_type(type, klass)
14
11
  types[type] = klass
15
- types["#{NULLABLE}(#{type})"] = Type::NullableType.new(klass) if nullable
16
- end
17
-
18
- # @return [Enum<String>]
19
- def type_names(nullable:)
20
- nullable ? types.keys : types.keys.grep_v(NULLABLE_RE)
21
12
  end
22
13
  end
23
14
  end
@@ -30,7 +30,7 @@ module ClickHouse
30
30
  def on_complete(env)
31
31
  summary = extract_summary(env.response_headers)
32
32
  logger.info("\e[1mSQL (#{duration_stats_log(env.body)})\e[0m #{query(env)};")
33
- logger.debug(body) if body && !query_in_body?(env)
33
+ logger.debug(body) if body
34
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")
35
35
  end
36
36
  # rubocop:enable Layout/LineLength
@@ -2,15 +2,14 @@
2
2
 
3
3
  module ClickHouse
4
4
  module Middleware
5
- class ParseCsv < FaradayMiddleware::ResponseMiddleware
5
+ class ParseCsv < ResponseBase
6
6
  Faraday::Response.register_middleware self => self
7
7
 
8
- dependency do
9
- require 'csv' unless defined?(CSV)
10
- end
8
+ # @param env [Faraday::Env]
9
+ def on_complete(env)
10
+ return unless content_type?(env, content_type)
11
11
 
12
- define_parser do |body, parser_options|
13
- CSV.parse(body, **Hash.new(parser_options)) unless body.strip.empty?
12
+ env.body = env.body.strip.empty? ? nil : CSV.parse(env.body)
14
13
  end
15
14
  end
16
15
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Middleware
5
+ class ParseJson < ResponseBase
6
+ Faraday::Response.register_middleware self => self
7
+
8
+ # @param env [Faraday::Env]
9
+ def on_complete(env)
10
+ return unless content_type?(env, content_type)
11
+
12
+ env.body = JSON.parse(env.body, config.json_load_options) unless env.body.strip.empty?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Middleware
5
+ class ParseJsonOj < ResponseBase
6
+ Faraday::Response.register_middleware self => self
7
+
8
+ # @param env [Faraday::Env]
9
+ def on_complete(env)
10
+ return unless content_type?(env, content_type)
11
+
12
+ env.body = Oj.load(env.body, config.oj_load_options) unless env.body.strip.empty?
13
+ end
14
+
15
+ private
16
+
17
+ def on_setup
18
+ require 'oj' unless defined?(Oj)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,22 +3,22 @@
3
3
  module ClickHouse
4
4
  module Middleware
5
5
  class RaiseError < Faraday::Middleware
6
- SUCCEED_STATUSES = (200..299).freeze
6
+ EXCEPTION_CODE_HEADER = 'x-clickhouse-exception-code'
7
7
 
8
8
  Faraday::Response.register_middleware self => self
9
9
 
10
- def call(environment)
11
- @app.call(environment).on_complete(&method(:on_complete))
10
+ # @param env [Faraday::Env]
11
+ def call(env)
12
+ super
12
13
  rescue Faraday::ConnectionFailed => e
13
14
  raise NetworkException, e.message, e.backtrace
14
15
  end
15
16
 
16
- private
17
-
17
+ # @param env [Faraday::Env]
18
18
  def on_complete(env)
19
- return if SUCCEED_STATUSES.include?(env.status)
20
-
21
- raise DbException, "[#{env.status}] #{env.body}"
19
+ if env.response_headers.include?(EXCEPTION_CODE_HEADER) || !env.success?
20
+ raise DbException, "[#{env.status}] #{env.body}"
21
+ end
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickHouse
4
+ module Middleware
5
+ class ResponseBase < Faraday::Middleware
6
+ CONTENT_TYPE_HEADER = 'content-type'
7
+
8
+ attr_reader :parser_options
9
+ attr_reader :content_type
10
+
11
+ def initialize(app = nil, parser_options: {}, content_type: nil, preserve_raw: false)
12
+ super(app)
13
+ @parser_options = parser_options
14
+ @content_type = content_type
15
+ @preserve_raw = preserve_raw
16
+ on_setup
17
+ end
18
+
19
+ # @return [Boolean]
20
+ # @param env [Faraday::Env]
21
+ # @param regexp [NilClass, Regexp]
22
+ def content_type?(env, regexp)
23
+ case regexp
24
+ when NilClass
25
+ false
26
+ when Regexp
27
+ regexp.match?(String(env[:response_headers][CONTENT_TYPE_HEADER]))
28
+ else
29
+ raise ArgumentError, "expected regexp got #{regexp.class}"
30
+ end
31
+ end
32
+
33
+ # @return [Config]
34
+ def config
35
+ parser_options.fetch(:config)
36
+ end
37
+
38
+ private
39
+
40
+ def on_setup
41
+ # require external dependencies here
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,8 +2,11 @@
2
2
 
3
3
  module ClickHouse
4
4
  module Middleware
5
+ autoload :ResponseBase, 'click_house/middleware/response_base'
5
6
  autoload :Logging, 'click_house/middleware/logging'
6
7
  autoload :ParseCsv, 'click_house/middleware/parse_csv'
8
+ autoload :ParseJsonOj, 'click_house/middleware/parse_json_oj'
9
+ autoload :ParseJson, 'click_house/middleware/parse_json'
7
10
  autoload :RaiseError, 'click_house/middleware/raise_error'
8
11
  end
9
12
  end
@@ -6,9 +6,8 @@ module ClickHouse
6
6
  extend Forwardable
7
7
  include Enumerable
8
8
 
9
- TYPE_ARGV_DELIM = ','
10
- NULLABLE = 'Nullable'
11
- NULLABLE_TYPE_RE = /#{NULLABLE}\((.+)\)/i.freeze
9
+ PLACEHOLDER_D = '%d'
10
+ PLACEHOLDER_S = '%s'
12
11
 
13
12
  def_delegators :to_a,
14
13
  :inspect, :each, :fetch, :length, :count, :size,
@@ -16,36 +15,6 @@ module ClickHouse
16
15
 
17
16
  attr_reader :meta, :data, :totals, :statistics, :rows_before_limit_at_least
18
17
 
19
- class << self
20
- # @return [Array<String, Array>]
21
- # * first element is name of "ClickHouse.types.keys"
22
- # * second element is extra arguments that should to be passed to <cast> function
23
- #
24
- # @input "DateTime('Europe/Moscow')"
25
- # @output "DateTime(%s)"
26
- #
27
- # @input "Nullable(Decimal(10, 5))"
28
- # @output "Nullable(Decimal(%s, %s))"
29
- #
30
- # @input "Decimal(10, 5)"
31
- # @output "Decimal(%s, %s)"
32
- def extract_type_info(type)
33
- type = type.gsub(NULLABLE_TYPE_RE, '\1')
34
- nullable = Regexp.last_match(1)
35
- argv = []
36
-
37
- type = type.gsub(/\((.+)\)/, '')
38
-
39
- if (match = Regexp.last_match(1))
40
- counter = Array.new(match.count(TYPE_ARGV_DELIM).next) { '%s' }
41
- type = "#{type}(#{counter.join("#{TYPE_ARGV_DELIM} ")})"
42
- argv = match.split("#{TYPE_ARGV_DELIM} ")
43
- end
44
-
45
- [nullable ? "#{NULLABLE}(#{type})" : type, argv]
46
- end
47
- end
48
-
49
18
  # @param meta [Array]
50
19
  # @param data [Array]
51
20
  # @param totals [Array|Hash|NilClass] Support for 'GROUP BY WITH TOTALS' modifier
@@ -62,20 +31,69 @@ module ClickHouse
62
31
  def to_a
63
32
  @to_a ||= data.each do |row|
64
33
  row.each do |name, value|
65
- casting = types.fetch(name)
66
- row[name] = casting.fetch(:caster).cast(value, *casting.fetch(:arguments))
34
+ row[name] = cast_type(types.fetch(name), value)
67
35
  end
68
36
  end
69
37
  end
70
38
 
39
+ # @return [Hash<String, Ast::Statement>]
71
40
  def types
72
41
  @types ||= meta.each_with_object({}) do |row, object|
73
- type_name, argv = self.class.extract_type_info(row.fetch('type'))
42
+ object[row.fetch('name')] = begin
43
+ current = Ast::Parser.new(row.fetch('type')).parse
44
+ assign_type(current)
45
+ current
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # @param stmt [Ast::Statement]
53
+ def assign_type(stmt)
54
+ stmt.caster = ClickHouse.types[stmt.name]
55
+
56
+ if stmt.caster.is_a?(Type::UndefinedType)
57
+ placeholders = stmt.arguments.map(&:placeholder)
58
+ stmt.caster = ClickHouse.types["#{stmt.name}(#{placeholders.join(', ')})"]
59
+ end
60
+
61
+ stmt.arguments.each(&method(:assign_type))
62
+ end
63
+
64
+ # @param stmt [Ast::Statement]
65
+ def cast_type(stmt, value)
66
+ return cast_container(stmt, value) if stmt.caster.container?
67
+ return cast_map(stmt, Hash(value)) if stmt.caster.map?
68
+ return cast_tuple(stmt, Array(value)) if stmt.caster.tuple?
69
+
70
+ stmt.caster.cast(value, *stmt.arguments.map(&:value))
71
+ end
72
+
73
+ # @return [Hash]
74
+ # @param stmt [Ast::Statement]
75
+ # @param hash [Hash]
76
+ def cast_map(stmt, hash)
77
+ raise ArgumentError, "expect hash got #{hash.class}" unless hash.is_a?(Hash)
78
+
79
+ key_type, value_type = stmt.arguments
80
+ hash.each_with_object({}) do |(key, value), object|
81
+ object[cast_type(key_type, key)] = cast_type(value_type, value)
82
+ end
83
+ end
84
+
85
+ # @param stmt [Ast::Statement]
86
+ def cast_container(stmt, value)
87
+ stmt.caster.cast_each(value) do |item|
88
+ # TODO: raise an error if multiple arguments
89
+ cast_type(stmt.arguments.first, item)
90
+ end
91
+ end
74
92
 
75
- object[row.fetch('name')] = {
76
- caster: ClickHouse.types[type_name],
77
- arguments: argv
78
- }
93
+ # @param stmt [Ast::Statement]
94
+ def cast_tuple(stmt, value)
95
+ value.map.with_index do |item, ix|
96
+ cast_type(stmt.arguments.fetch(ix), item)
79
97
  end
80
98
  end
81
99
  end
@@ -4,5 +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 :Tokenize, 'click_house/response/tokenize'
7
8
  end
8
9
  end
@@ -3,22 +3,16 @@
3
3
  module ClickHouse
4
4
  module Type
5
5
  class ArrayType < BaseType
6
- attr_reader :subtype
7
-
8
- def initialize(subtype)
9
- @subtype = subtype
6
+ def cast_each(value, *_argv, &block)
7
+ value.map(&block)
10
8
  end
11
9
 
12
- def cast(value, *)
13
- value
10
+ def container?
11
+ true
14
12
  end
15
13
 
16
- def serialize(array, *argv)
17
- return unless array.is_a?(Array)
18
-
19
- array.map do |value|
20
- subtype.serialize(value, *argv)
21
- end
14
+ def ddl?
15
+ false
22
16
  end
23
17
  end
24
18
  end