clickhouse-activerecord 1.1.3 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 419c50a4b99b183f729939034e8f6b671bf899fb0ef93fc97f105798fa437aa1
4
- data.tar.gz: 7508a2ed443d64565ca6b1f5af48bf076cf40d2eae91fd3c3187cae05371a864
3
+ metadata.gz: c3301f161ab9a3c94507a1d3a989e32710628746eeeac895966e64dd25579dd4
4
+ data.tar.gz: 2ab20aabc31a593987fb8905b5c58f023025a24cf447d0d922644ebbcff63397
5
5
  SHA512:
6
- metadata.gz: 8e8607fa219f6e17e22a3029ba564e6d3d6d2bd39cf86f639d277a6e83fe2ffb1a47e93fceb8d20c4de6a7e9679f8a15b40ee744bfa445be14e6e0693dde4a0e
7
- data.tar.gz: 5e58b79aab91a64387dc74821a75710c70738791227084a3a6ce29e72827a40e0458d48643a99c14b6d2dc6c94bbed8672d31051d7501084aa3a7e0bc27ef6a1
6
+ metadata.gz: f2d35a1489031909c0a2b8c2f8feb5624b93854bfbcabd9e39e006874033d0b534d568e72deabb795fcbeb1c0a3416966df8168b6f10d1ef64a3bb36d96b838f
7
+ data.tar.gz: 9c6be79ff71e1207cd2b162e909db8b7db8a2276ce17351b2b2fc38097dc47f8c4dd8386cfacdc37bd1ccd3cfc28072dc45bd09b155737ef28b63a3a55f35def
@@ -27,7 +27,7 @@ jobs:
27
27
  - ruby: 3.2
28
28
  rails: 7.1.3
29
29
  - ruby: 3.2
30
- rails: 7.2.0
30
+ rails: 7.2.1
31
31
  clickhouse: [ '22.1', '24.6' ]
32
32
 
33
33
  steps:
@@ -49,7 +49,7 @@ jobs:
49
49
  ruby-version: ${{ matrix.version.ruby }}
50
50
  bundler-cache: true
51
51
 
52
- - run: bundle exec rspec spec/single
52
+ - run: bundle exec rspec spec/single --format progress
53
53
 
54
54
  tests_cluster:
55
55
  name: Testing cluster server
@@ -94,4 +94,4 @@ jobs:
94
94
  ruby-version: ${{ matrix.version.ruby }}
95
95
  bundler-cache: true
96
96
 
97
- - run: bundle exec rspec spec/cluster
97
+ - run: bundle exec rspec spec/cluster --format progress
data/README.md CHANGED
@@ -237,12 +237,13 @@ class CreateDataItems < ActiveRecord::Migration[7.1]
237
237
  end
238
238
  ```
239
239
 
240
- Create table with custom column structure:
240
+ Create table with custom column structure and codec compression:
241
241
 
242
242
  ```ruby
243
243
  class CreateDataItems < ActiveRecord::Migration[7.1]
244
244
  def change
245
245
  create_table "data_items", id: false, options: "MergeTree PARTITION BY toYYYYMM(timestamp) ORDER BY timestamp", force: :cascade do |t|
246
+ t.integer :user_id, limit: 8, codec: 'DoubleDelta, LZ4'
246
247
  t.column "timestamp", "DateTime('UTC') CODEC(DoubleDelta, LZ4)"
247
248
  end
248
249
  end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Clickhouse
4
+ class Column < ActiveRecord::ConnectionAdapters::Column
5
+
6
+ attr_reader :codec
7
+
8
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, codec: nil, **args)
9
+ super
10
+ @codec = codec
11
+ end
12
+
13
+ private
14
+
15
+ def deduplicated
16
+ self
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -26,6 +26,8 @@ module ActiveRecord
26
26
  def deserialize(value)
27
27
  if value.is_a?(::Hash)
28
28
  value.map { |k, item| [k.to_s, deserialize(item)] }.to_h
29
+ elsif value.is_a?(::Array)
30
+ value.map { |item| deserialize(item) }
29
31
  else
30
32
  return value if value.nil?
31
33
  case @subtype
@@ -44,6 +46,8 @@ module ActiveRecord
44
46
  def serialize(value)
45
47
  if value.is_a?(::Hash)
46
48
  value.map { |k, item| [k.to_s, serialize(item)] }.to_h
49
+ elsif value.is_a?(::Array)
50
+ value.map { |item| serialize(item) }
47
51
  else
48
52
  return value if value.nil?
49
53
  case @subtype
@@ -33,9 +33,15 @@ module ActiveRecord
33
33
  if options[:array]
34
34
  sql.gsub!(/\s+(.*)/, ' Array(\1)')
35
35
  end
36
- if options[:map]
36
+ if options[:map] == :array
37
+ sql.gsub!(/\s+(.*)/, ' Map(String, Array(\1))')
38
+ end
39
+ if options[:map] == true
37
40
  sql.gsub!(/\s+(.*)/, ' Map(String, \1)')
38
41
  end
42
+ if options[:codec]
43
+ sql.gsub!(/\s+(.*)/, " \\1 CODEC(#{options[:codec]})")
44
+ end
39
45
  sql.gsub!(/(\sString)\(\d+\)/, '\1')
40
46
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
41
47
  sql
@@ -8,6 +8,8 @@ module ActiveRecord
8
8
  module SchemaStatements
9
9
  DEFAULT_RESPONSE_FORMAT = 'JSONCompactEachRowWithNamesAndTypes'.freeze
10
10
 
11
+ DB_EXCEPTION_REGEXP = /\ACode:\s+\d+\.\s+DB::Exception:/.freeze
12
+
11
13
  def execute(sql, name = nil, settings: {})
12
14
  do_execute(sql, name, settings: settings)
13
15
  end
@@ -183,7 +185,9 @@ module ActiveRecord
183
185
  def process_response(res, format, sql = nil)
184
186
  case res.code.to_i
185
187
  when 200
186
- if res.body.to_s.include?("DB::Exception")
188
+ body = res.body
189
+
190
+ if body.include?("DB::Exception") && body.match?(DB_EXCEPTION_REGEXP)
187
191
  raise ActiveRecord::ActiveRecordError, "Response code: #{res.code}:\n#{res.body}#{sql ? "\nQuery: #{sql}" : ''}"
188
192
  else
189
193
  format_body_response(res.body, format)
@@ -221,7 +225,7 @@ module ActiveRecord
221
225
  default_value = extract_value_from_default(field[3], field[2])
222
226
  default_function = extract_default_function(field[3])
223
227
  default_value = lookup_cast_type(sql_type).cast(default_value)
224
- ClickhouseColumn.new(field[0], default_value, type_metadata, field[1].include?('Nullable'), default_function)
228
+ Clickhouse::Column.new(field[0], default_value, type_metadata, field[1].include?('Nullable'), default_function, codec: field[5].presence)
225
229
  end
226
230
 
227
231
  protected
@@ -94,10 +94,15 @@ module ActiveRecord
94
94
  args.each { |name| column(name, kind, **options.except(:limit)) }
95
95
  end
96
96
 
97
+ def column(name, type, index: nil, **options)
98
+ options[:null] = false if type.match?(/Nullable\([^)]+\)/)
99
+ super(name, type, index: index, **options)
100
+ end
101
+
97
102
  private
98
103
 
99
104
  def valid_column_definition_options
100
- super + [:array, :low_cardinality, :fixed_string, :value, :type, :map]
105
+ super + [:array, :low_cardinality, :fixed_string, :value, :type, :map, :codec]
101
106
  end
102
107
  end
103
108
 
@@ -2,18 +2,21 @@
2
2
 
3
3
  require 'arel/visitors/clickhouse'
4
4
  require 'arel/nodes/final'
5
+ require 'arel/nodes/grouping_sets'
5
6
  require 'arel/nodes/settings'
6
7
  require 'arel/nodes/using'
8
+ require 'arel/nodes/limit_by'
7
9
  require 'active_record/connection_adapters/clickhouse/oid/array'
8
10
  require 'active_record/connection_adapters/clickhouse/oid/date'
9
11
  require 'active_record/connection_adapters/clickhouse/oid/date_time'
10
12
  require 'active_record/connection_adapters/clickhouse/oid/big_integer'
11
13
  require 'active_record/connection_adapters/clickhouse/oid/map'
12
14
  require 'active_record/connection_adapters/clickhouse/oid/uuid'
15
+ require 'active_record/connection_adapters/clickhouse/column'
13
16
  require 'active_record/connection_adapters/clickhouse/quoting'
14
- require 'active_record/connection_adapters/clickhouse/schema_definitions'
15
17
  require 'active_record/connection_adapters/clickhouse/schema_creation'
16
18
  require 'active_record/connection_adapters/clickhouse/schema_statements'
19
+ require 'active_record/connection_adapters/clickhouse/table_definition'
17
20
  require 'net/http'
18
21
  require 'openssl'
19
22
 
@@ -47,7 +50,12 @@ module ActiveRecord
47
50
 
48
51
  module ModelSchema
49
52
  module ClassMethods
50
- delegate :final, :final!, :settings, :settings!, :window, :window!, to: :all
53
+ delegate :final, :final!,
54
+ :group_by_grouping_sets, :group_by_grouping_sets!,
55
+ :settings, :settings!,
56
+ :window, :window!,
57
+ :limit_by, :limit_by!,
58
+ to: :all
51
59
 
52
60
  def is_view
53
61
  @is_view || false
@@ -70,13 +78,6 @@ module ActiveRecord
70
78
  register "clickhouse", "ActiveRecord::ConnectionAdapters::ClickhouseAdapter", "active_record/connection_adapters/clickhouse_adapter"
71
79
  end
72
80
 
73
- class ClickhouseColumn < Column
74
- private
75
- def deduplicated
76
- self
77
- end
78
- end
79
-
80
81
  class ClickhouseAdapter < AbstractAdapter
81
82
  include Clickhouse::Quoting
82
83
 
@@ -351,8 +352,8 @@ module ActiveRecord
351
352
  end
352
353
  end
353
354
 
354
- def create_function(name, body)
355
- fd = "CREATE FUNCTION #{apply_cluster(quote_table_name(name))} AS #{body}"
355
+ def create_function(name, body, **options)
356
+ fd = "CREATE#{' OR REPLACE' if options[:force]} FUNCTION #{apply_cluster(quote_table_name(name))} AS #{body}"
356
357
  do_execute(fd, format: nil)
357
358
  end
358
359
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class GroupingSets < Arel::Nodes::Unary
6
+
7
+ def initialize(expr)
8
+ super
9
+ @expr = wrap_grouping_sets(expr)
10
+ end
11
+
12
+ private
13
+
14
+ def wrap_grouping_sets(sets)
15
+ sets.map do |element|
16
+ # See Arel::SelectManager#group
17
+ case element
18
+ when Array
19
+ wrap_grouping_sets(element)
20
+ when String
21
+ ::Arel::Nodes::SqlLiteral.new(element)
22
+ when Symbol
23
+ ::Arel::Nodes::SqlLiteral.new(element.to_s)
24
+ else
25
+ element
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ module Arel # :nodoc: all
2
+ module Nodes
3
+ class LimitBy < Arel::Nodes::Unary
4
+ attr_reader :column
5
+
6
+ def initialize(limit, column)
7
+ raise ArgumentError, 'Limit should be an integer' unless limit.is_a?(Integer)
8
+ raise ArgumentError, 'Limit should be a positive integer' unless limit >= 0
9
+ raise ArgumentError, 'Column should be a Symbol or String' unless column.is_a?(String) || column.is_a?(Symbol)
10
+
11
+ @column = column
12
+
13
+ super(limit)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -25,6 +25,7 @@ module Arel
25
25
  end
26
26
 
27
27
  def visit_Arel_Nodes_SelectOptions(o, collector)
28
+ maybe_visit o.limit_by, collector
28
29
  maybe_visit o.settings, super
29
30
  end
30
31
 
@@ -45,6 +46,11 @@ module Arel
45
46
  collector
46
47
  end
47
48
 
49
+ def visit_Arel_Nodes_GroupingSets(o, collector)
50
+ collector << 'GROUPING SETS '
51
+ grouping_array_or_grouping_element(o.expr, collector)
52
+ end
53
+
48
54
  def visit_Arel_Nodes_Settings(o, collector)
49
55
  return collector if o.expr.empty?
50
56
 
@@ -64,6 +70,11 @@ module Arel
64
70
  collector
65
71
  end
66
72
 
73
+ def visit_Arel_Nodes_LimitBy(o, collector)
74
+ collector << "LIMIT #{o.expr} BY #{o.column}"
75
+ collector
76
+ end
77
+
67
78
  def visit_Arel_Nodes_Matches(o, collector)
68
79
  op = o.case_sensitive ? " LIKE " : " ILIKE "
69
80
  infix_value o, collector, op
@@ -95,6 +106,25 @@ module Arel
95
106
  @connection.sanitize_as_setting_name(value)
96
107
  end
97
108
 
109
+ private
110
+
111
+ # Utilized by GroupingSet, Cube & RollUp visitors to
112
+ # handle grouping aggregation semantics
113
+ def grouping_array_or_grouping_element(o, collector)
114
+ if o.is_a? Array
115
+ collector << '( '
116
+ o.each_with_index do |el, i|
117
+ collector << ', ' if i > 0
118
+ grouping_array_or_grouping_element el, collector
119
+ end
120
+ collector << ' )'
121
+ elsif o.respond_to? :expr
122
+ visit o.expr, collector
123
+ else
124
+ visit o, collector
125
+ end
126
+ end
127
+
98
128
  end
99
129
  end
100
130
  end
@@ -35,7 +35,7 @@ module ClickhouseActiverecord
35
35
  # super(table.gsub(/^\.inner\./, ''), stream)
36
36
 
37
37
  # detect view table
38
- match = sql.match(/^CREATE\s+(MATERIALIZED\s+)?VIEW/)
38
+ view_match = sql.match(/^CREATE\s+(MATERIALIZED\s+)?VIEW\s+\S+\s+(?:TO (\S+))?/)
39
39
  end
40
40
 
41
41
  # Copy from original dumper
@@ -50,8 +50,9 @@ module ClickhouseActiverecord
50
50
 
51
51
  unless simple
52
52
  # Add materialize flag
53
- tbl.print ', view: true' if match
54
- tbl.print ', materialized: true' if match && match[1].presence
53
+ tbl.print ', view: true' if view_match
54
+ tbl.print ', materialized: true' if view_match && view_match[1].presence
55
+ tbl.print ", to: \"#{view_match[2]}\"" if view_match && view_match[2].presence
55
56
  end
56
57
 
57
58
  if (id = columns.detect { |c| c.name == 'id' })
@@ -75,10 +76,10 @@ module ClickhouseActiverecord
75
76
  tbl.puts ", force: :cascade do |t|"
76
77
 
77
78
  # then dump all non-primary key columns
78
- if simple || !match
79
+ if simple || !view_match
79
80
  columns.each do |column|
80
81
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
81
- next if column.name == pk
82
+ next if column.name == pk && column.name == "id"
82
83
  type, colspec = column_spec(column)
83
84
  name = column.name =~ (/\./) ? "\"`#{column.name}`\"" : column.name.inspect
84
85
  tbl.print " t.#{type} #{name}"
@@ -111,8 +112,11 @@ module ClickhouseActiverecord
111
112
  def function(function, stream)
112
113
  stream.puts " # FUNCTION: #{function}"
113
114
  sql = @connection.show_create_function(function)
114
- stream.puts " # SQL: #{sql}" if sql
115
- stream.puts " create_function \"#{function}\", \"#{sql.gsub(/^CREATE FUNCTION (.*?) AS/, '').strip}\"" if sql
115
+ if sql
116
+ stream.puts " # SQL: #{sql}"
117
+ stream.puts " create_function \"#{function}\", \"#{sql.gsub(/^CREATE FUNCTION (.*?) AS/, '').strip}\", force: true"
118
+ stream.puts
119
+ end
116
120
  end
117
121
 
118
122
  def format_options(options)
@@ -141,23 +145,32 @@ module ClickhouseActiverecord
141
145
  end
142
146
 
143
147
  def schema_array(column)
144
- (column.sql_type =~ /Array?\(/).nil? ? nil : true
148
+ (column.sql_type =~ /Array\(/).nil? ? nil : true
145
149
  end
146
150
 
147
151
  def schema_map(column)
148
- (column.sql_type =~ /Map?\(/).nil? ? nil : true
152
+ if column.sql_type =~ /Map\(([^,]+),\s*(Array)\)/
153
+ return :array
154
+ end
155
+
156
+ (column.sql_type =~ /Map\(/).nil? ? nil : true
149
157
  end
150
158
 
151
159
  def schema_low_cardinality(column)
152
- (column.sql_type =~ /LowCardinality?\(/).nil? ? nil : true
160
+ (column.sql_type =~ /LowCardinality\(/).nil? ? nil : true
153
161
  end
154
162
 
163
+ # @param [ActiveRecord::ConnectionAdapters::Clickhouse::Column] column
155
164
  def prepare_column_options(column)
156
165
  spec = {}
157
166
  spec[:unsigned] = schema_unsigned(column)
158
167
  spec[:array] = schema_array(column)
159
168
  spec[:map] = schema_map(column)
169
+ if spec[:map] == :array
170
+ spec[:array] = nil
171
+ end
160
172
  spec[:low_cardinality] = schema_low_cardinality(column)
173
+ spec[:codec] = column.codec.inspect if column.codec
161
174
  spec.merge(super).compact
162
175
  end
163
176
 
@@ -67,6 +67,8 @@ module ClickhouseActiverecord
67
67
  next
68
68
  elsif sql =~ /^INSERT INTO/
69
69
  connection.do_execute(sql, nil, format: nil)
70
+ elsif sql =~ /^CREATE .*?FUNCTION/
71
+ connection.do_execute(sql, nil, format: nil)
70
72
  else
71
73
  connection.execute(sql)
72
74
  end
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '1.1.3'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -47,6 +47,33 @@ module CoreExtensions
47
47
  self
48
48
  end
49
49
 
50
+ # GROUPING SETS allows you to specify multiple groupings in the GROUP BY clause.
51
+ # Whereas GROUP BY CUBE generates all possible groupings, GROUP BY GROUPING SETS generates only the specified groupings.
52
+ # For example:
53
+ #
54
+ # users = User.group_by_grouping_sets([], [:name], [:name, :age]).select(:name, :age, 'count(*)')
55
+ # # SELECT name, age, count(*) FROM users GROUP BY GROUPING SETS ( (), (name), (name, age) )
56
+ #
57
+ # which is generally equivalent to:
58
+ # # SELECT NULL, NULL, count(*) FROM users
59
+ # # UNION ALL
60
+ # # SELECT name, NULL, count(*) FROM users GROUP BY name
61
+ # # UNION ALL
62
+ # # SELECT name, age, count(*) FROM users GROUP BY name, age
63
+ #
64
+ # Raises <tt>ArgumentError</tt> if no grouping sets are specified are provided.
65
+ def group_by_grouping_sets(*grouping_sets)
66
+ raise ArgumentError, 'The method .group_by_grouping_sets() must contain arguments.' if grouping_sets.blank?
67
+
68
+ spawn.group_by_grouping_sets!(*grouping_sets)
69
+ end
70
+
71
+ def group_by_grouping_sets!(*grouping_sets) # :nodoc:
72
+ grouping_sets = grouping_sets.map { |set| arel_columns(set) }
73
+ self.group_values += [::Arel::Nodes::GroupingSets.new(grouping_sets)]
74
+ self
75
+ end
76
+
50
77
  # The USING clause specifies one or more columns to join, which establishes the equality of these columns. For example:
51
78
  #
52
79
  # users = User.joins(:joins).using(:event_name, :date)
@@ -81,6 +108,24 @@ module CoreExtensions
81
108
  self
82
109
  end
83
110
 
111
+ # The LIMIT BY clause permit to improve deduplication based on a unique key, it has better performances than
112
+ # the GROUP BY clause
113
+ #
114
+ # users = User.limit_by(1, id)
115
+ # # SELECT users.* FROM users LIMIT 1 BY id
116
+ #
117
+ # An <tt>ActiveRecord::ActiveRecordError</tt> will be reaised if database is not Clickhouse.
118
+ # @param [Array] opts
119
+ def limit_by(*opts)
120
+ spawn.limit_by!(*opts)
121
+ end
122
+
123
+ # @param [Array] opts
124
+ def limit_by!(*opts)
125
+ @values[:limit_by] = *opts
126
+ self
127
+ end
128
+
84
129
  private
85
130
 
86
131
  def check_command(cmd)
@@ -95,6 +140,7 @@ module CoreExtensions
95
140
  end
96
141
 
97
142
  arel.final! if @values[:final].present?
143
+ arel.limit_by(*@values[:limit_by]) if @values[:limit_by].present?
98
144
  arel.settings(@values[:settings]) if @values[:settings].present?
99
145
  arel.using(@values[:using]) if @values[:using].present?
100
146
  arel.windows(@values[:windows]) if @values[:windows].present?
@@ -2,15 +2,18 @@ module CoreExtensions
2
2
  module Arel # :nodoc: all
3
3
  module Nodes
4
4
  module SelectStatement
5
- attr_accessor :settings
5
+ attr_accessor :limit_by, :settings
6
6
 
7
7
  def initialize(relation = nil)
8
8
  super
9
+ @limit_by = nil
9
10
  @settings = nil
10
11
  end
11
12
 
12
13
  def eql?(other)
13
- super && settings == other.settings
14
+ super &&
15
+ limit_by == other.limit_by &&
16
+ settings == other.settings
14
17
  end
15
18
  end
16
19
  end
@@ -29,6 +29,11 @@ module CoreExtensions
29
29
  @ctx.source.right.last.right = ::Arel::Nodes::Using.new(::Arel.sql(exprs.join(',')))
30
30
  self
31
31
  end
32
+
33
+ def limit_by(*exprs)
34
+ @ast.limit_by = ::Arel::Nodes::LimitBy.new(*exprs)
35
+ self
36
+ end
32
37
  end
33
38
  end
34
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clickhouse-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Odintsov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-27 00:00:00.000000000 Z
11
+ date: 2024-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -106,6 +106,7 @@ files:
106
106
  - bin/console
107
107
  - bin/setup
108
108
  - clickhouse-activerecord.gemspec
109
+ - lib/active_record/connection_adapters/clickhouse/column.rb
109
110
  - lib/active_record/connection_adapters/clickhouse/oid/array.rb
110
111
  - lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb
111
112
  - lib/active_record/connection_adapters/clickhouse/oid/date.rb
@@ -114,10 +115,12 @@ files:
114
115
  - lib/active_record/connection_adapters/clickhouse/oid/uuid.rb
115
116
  - lib/active_record/connection_adapters/clickhouse/quoting.rb
116
117
  - lib/active_record/connection_adapters/clickhouse/schema_creation.rb
117
- - lib/active_record/connection_adapters/clickhouse/schema_definitions.rb
118
118
  - lib/active_record/connection_adapters/clickhouse/schema_statements.rb
119
+ - lib/active_record/connection_adapters/clickhouse/table_definition.rb
119
120
  - lib/active_record/connection_adapters/clickhouse_adapter.rb
120
121
  - lib/arel/nodes/final.rb
122
+ - lib/arel/nodes/grouping_sets.rb
123
+ - lib/arel/nodes/limit_by.rb
121
124
  - lib/arel/nodes/settings.rb
122
125
  - lib/arel/nodes/using.rb
123
126
  - lib/arel/visitors/clickhouse.rb