clickhouse-activerecord 0.6.1 → 1.0.0

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: 74728f97bde396aae27436dd6b7ec8d57ec172e818122f9094d994583c92781f
4
- data.tar.gz: 64e08f74a95d755338b489b32f2aade49685b6b604a3d02d2f232ca9f24b7b23
3
+ metadata.gz: 823fdbf17f27c65446c01b1599a3c61d9fa9b310d7ce153e9c64a912719e552e
4
+ data.tar.gz: 5b6dd44cdc44b2d1350e0f1c5937cf08051a5ff7a4f73cf56a5dc7567b4be800
5
5
  SHA512:
6
- metadata.gz: 959c753aaf61bcef02dd336953d1bd88bc0d2d3762bc5f96e3816699685c809da2f4ad7646201570892a12fad32760a80d69ff6292b172fb6e9c518c13dc622d
7
- data.tar.gz: e7ee279cf5dc1b8168448f8c657550e2cb418121da1ef58c1d53989f8a7cc678527fcb7678cc0ba903d9e5d9e108450c9da28d03ba0e6d9364738d1c2f60e414
6
+ metadata.gz: 5a8d48c078f5bf79a50fddf9b29ade13694019e8666c8c071e5b1ee7ab5b16c7e92116b794a82ce3fa6f927f0bd1186e7e87a65ed5f4eda0f1e6ed113f590de4
7
+ data.tar.gz: 2be66ae7ac2cb71b20486685f00552890559f2944777d8557aabb60d2318baa868c3c4bc0cf4be82a97eeee920de0049851771253129022e8fbbb0758190f917
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Clickhouse::Activerecord
2
2
 
3
- A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 5.2.
3
+ A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 7.1.
4
4
  Support ClickHouse version from 22.0 LTS.
5
5
 
6
6
  ## Installation
@@ -50,41 +50,31 @@ class ActionView < ActiveRecord::Base
50
50
  end
51
51
  ```
52
52
 
53
- ## Usage in Rails 5
53
+ ## Usage in Rails
54
54
 
55
55
  Add your `database.yml` connection information with postfix `_clickhouse` for you environment:
56
56
 
57
57
  ```yml
58
- development_clickhouse:
58
+ development:
59
59
  adapter: clickhouse
60
60
  database: database
61
61
  ```
62
62
 
63
- Add to your model:
63
+ Your model example:
64
64
 
65
65
  ```ruby
66
66
  class Action < ActiveRecord::Base
67
- establish_connection "#{Rails.env}_clickhouse".to_sym
68
67
  end
69
68
  ```
70
69
 
71
70
  For materialized view model add:
72
71
  ```ruby
73
72
  class ActionView < ActiveRecord::Base
74
- establish_connection "#{Rails.env}_clickhouse".to_sym
75
73
  self.is_view = true
76
74
  end
77
75
  ```
78
76
 
79
- Or global connection:
80
-
81
- ```yml
82
- development:
83
- adapter: clickhouse
84
- database: database
85
- ```
86
-
87
- ## Usage in Rails 6 with second database
77
+ ## Usage in Rails with second database
88
78
 
89
79
  Add your `database.yml` connection information for you environment:
90
80
 
@@ -102,31 +92,31 @@ Connection [Multiple Databases with Active Record](https://guides.rubyonrails.or
102
92
 
103
93
  ```ruby
104
94
  class Action < ActiveRecord::Base
105
- connects_to database: { writing: :clickhouse, reading: :clickhouse }
95
+ establish_connection :clickhouse
106
96
  end
107
97
  ```
108
98
 
109
99
  ### Rake tasks
110
100
 
111
- **Note!** For Rails 6 you can use default rake tasks if you configure `migrations_paths` in your `database.yml`, for example: `rake db:migrate`
112
-
113
101
  Create / drop / purge / reset database:
114
102
 
115
- $ rake clickhouse:create
116
- $ rake clickhouse:drop
117
- $ rake clickhouse:purge
118
- $ rake clickhouse:reset
103
+ $ rake db:create
104
+ $ rake db:drop
105
+ $ rake db:purge
106
+ $ rake db:reset
119
107
 
120
- Prepare system tables for rails:
108
+ Or with multiple databases:
121
109
 
122
- $ rake clickhouse:prepare_schema_migration_table
123
- $ rake clickhouse:prepare_internal_metadata_table
110
+ $ rake db:create:clickhouse
111
+ $ rake db:drop:clickhouse
112
+ $ rake db:purge:clickhouse
113
+ $ rake db:reset:clickhouse
124
114
 
125
115
  Migration:
126
116
 
127
117
  $ rails g clickhouse_migration MIGRATION_NAME COLUMNS
128
- $ rake clickhouse:migrate
129
- $ rake clickhouse:rollback
118
+ $ rake db:migrate
119
+ $ rake db:rollback
130
120
 
131
121
  ### Dump / Load for multiple using databases
132
122
 
@@ -195,20 +185,20 @@ User.joins(:actions).using(:group_id)
195
185
  Integer types are unsigned by default. Specify signed values with `:unsigned =>
196
186
  false`. The default integer is `UInt32`
197
187
 
198
- | Type (bit size) | Range | :limit (byte size) |
199
- | :--- | :----: | ---: |
200
- | Int8 | -128 to 127 | 1 |
201
- | Int16 | -32768 to 32767 | 2 |
202
- | Int32 | -2147483648 to 2,147,483,647 | 3,4 |
203
- | Int64 | -9223372036854775808 to 9223372036854775807] | 5,6,7,8 |
204
- | Int128 | ... | 9 - 15 |
205
- | Int256 | ... | 16+ |
206
- | UInt8 | 0 to 255 | 1 |
207
- | UInt16 | 0 to 65,535 | 2 |
208
- | UInt32 | 0 to 4,294,967,295 | 3,4 |
209
- | UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
210
- | UInt256 | 0 to ... | 8+ |
211
- | Array | ... | ... |
188
+ | Type (bit size) | Range | :limit (byte size) |
189
+ |:----------------|:--------------------------------------------:|-------------------:|
190
+ | Int8 | -128 to 127 | 1 |
191
+ | Int16 | -32768 to 32767 | 2 |
192
+ | Int32 | -2147483648 to 2,147,483,647 | 3,4 |
193
+ | Int64 | -9223372036854775808 to 9223372036854775807] | 5,6,7,8 |
194
+ | Int128 | ... | 9 - 15 |
195
+ | Int256 | ... | 16+ |
196
+ | UInt8 | 0 to 255 | 1 |
197
+ | UInt16 | 0 to 65,535 | 2 |
198
+ | UInt32 | 0 to 4,294,967,295 | 3,4 |
199
+ | UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
200
+ | UInt256 | 0 to ... | 8+ |
201
+ | Array | ... | ... |
212
202
 
213
203
  Example:
214
204
 
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.add_runtime_dependency 'bundler', '>= 1.13.4'
27
- spec.add_runtime_dependency 'activerecord', '>= 5.2', '< 7'
27
+ spec.add_runtime_dependency 'activerecord', '>= 7.1'
28
28
 
29
29
  spec.add_development_dependency 'rake', '~> 13.0'
30
30
  spec.add_development_dependency 'rspec', '~> 3.4'
@@ -10,13 +10,13 @@ module ActiveRecord
10
10
  do_execute(sql, name, settings: settings)
11
11
  end
12
12
 
13
- def exec_insert(sql, name, _binds, _pk = nil, _sequence_name = nil)
13
+ def exec_insert(sql, name, _binds, _pk = nil, _sequence_name = nil, returning: nil)
14
14
  new_sql = sql.dup.sub(/ (DEFAULT )?VALUES/, " VALUES")
15
15
  do_execute(new_sql, name, format: nil)
16
16
  true
17
17
  end
18
18
 
19
- def exec_query(sql, name = nil, binds = [], prepare: false)
19
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false)
20
20
  result = do_execute(sql, name)
21
21
  ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'], result['meta'].map { |m| [m['name'], type_map.lookup(m['type'])] }.to_h)
22
22
  rescue ActiveRecord::ActiveRecordError => e
@@ -137,7 +137,7 @@ module ActiveRecord
137
137
  Clickhouse::TableDefinition.new(self, table_name, **options)
138
138
  end
139
139
 
140
- def new_column_from_field(table_name, field)
140
+ def new_column_from_field(table_name, field, _definitions)
141
141
  sql_type = field[1]
142
142
  type_metadata = fetch_type_metadata(sql_type)
143
143
  default = field[3]
@@ -136,7 +136,11 @@ module ActiveRecord
136
136
 
137
137
  # Support SchemaMigration from v5.2.2 to v6+
138
138
  def schema_migration # :nodoc:
139
- ClickhouseActiverecord::SchemaMigration
139
+ ClickhouseActiverecord::SchemaMigration.new(self)
140
+ end
141
+
142
+ def internal_metadata # :nodoc:
143
+ ClickhouseActiverecord::InternalMetadata.new(self)
140
144
  end
141
145
 
142
146
  def migrations_paths
@@ -144,7 +148,7 @@ module ActiveRecord
144
148
  end
145
149
 
146
150
  def migration_context # :nodoc:
147
- ClickhouseActiverecord::MigrationContext.new(migrations_paths, schema_migration)
151
+ ClickhouseActiverecord::MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
148
152
  end
149
153
 
150
154
  def arel_visitor # :nodoc:
@@ -159,66 +163,73 @@ module ActiveRecord
159
163
  !native_database_types[type].nil?
160
164
  end
161
165
 
162
- def extract_limit(sql_type) # :nodoc:
163
- case sql_type
164
- when /(Nullable)?\(?String\)?/
165
- super('String')
166
- when /(Nullable)?\(?U?Int8\)?/
167
- 1
168
- when /(Nullable)?\(?U?Int16\)?/
169
- 2
170
- when /(Nullable)?\(?U?Int32\)?/
171
- nil
172
- when /(Nullable)?\(?U?Int64\)?/
173
- 8
174
- else
175
- super
166
+ class << self
167
+ def extract_limit(sql_type) # :nodoc:
168
+ case sql_type
169
+ when /(Nullable)?\(?String\)?/
170
+ super('String')
171
+ when /(Nullable)?\(?U?Int8\)?/
172
+ 1
173
+ when /(Nullable)?\(?U?Int16\)?/
174
+ 2
175
+ when /(Nullable)?\(?U?Int32\)?/
176
+ nil
177
+ when /(Nullable)?\(?U?Int64\)?/
178
+ 8
179
+ else
180
+ super
181
+ end
176
182
  end
177
- end
178
183
 
179
- # `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
180
- # except this permits a space after the comma
184
+ # `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
185
+ # except this permits a space after the comma
181
186
 
182
- def extract_scale(sql_type)
183
- case sql_type
184
- when /\((\d+)\)/ then 0
185
- when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
187
+ def extract_scale(sql_type)
188
+ case sql_type
189
+ when /\((\d+)\)/ then 0
190
+ when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
191
+ end
186
192
  end
187
- end
188
193
 
189
- def extract_precision(sql_type)
190
- $1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
191
- end
192
-
193
- def initialize_type_map(m) # :nodoc:
194
- super
195
- register_class_with_limit m, %r(String), Type::String
196
- register_class_with_limit m, 'Date', Clickhouse::OID::Date
197
- register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
198
-
199
- register_class_with_limit m, %r(Int8), Type::Integer
200
- register_class_with_limit m, %r(Int16), Type::Integer
201
- register_class_with_limit m, %r(Int32), Type::Integer
202
- register_class_with_limit m, %r(Int64), Type::Integer
203
- register_class_with_limit m, %r(Int128), Type::Integer
204
- register_class_with_limit m, %r(Int256), Type::Integer
205
-
206
- register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
207
- register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
208
- register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
209
- register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
210
- #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
211
- register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
212
- # register_class_with_limit m, %r(Array), Clickhouse::OID::Array
213
- m.register_type(%r(Array)) do |sql_type|
214
- Clickhouse::OID::Array.new(sql_type)
194
+ def extract_precision(sql_type)
195
+ $1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
196
+ end
197
+
198
+ def initialize_type_map(m) # :nodoc:
199
+ super
200
+ register_class_with_limit m, %r(String), Type::String
201
+ register_class_with_limit m, 'Date', Clickhouse::OID::Date
202
+ register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
203
+
204
+ register_class_with_limit m, %r(Int8), Type::Integer
205
+ register_class_with_limit m, %r(Int16), Type::Integer
206
+ register_class_with_limit m, %r(Int32), Type::Integer
207
+ register_class_with_limit m, %r(Int64), Type::Integer
208
+ register_class_with_limit m, %r(Int128), Type::Integer
209
+ register_class_with_limit m, %r(Int256), Type::Integer
210
+
211
+ register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
212
+ register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
213
+ register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
214
+ register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
215
+ #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
216
+ register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
217
+ # register_class_with_limit m, %r(Array), Clickhouse::OID::Array
218
+ m.register_type(%r(Array)) do |sql_type|
219
+ Clickhouse::OID::Array.new(sql_type)
220
+ end
215
221
  end
216
222
  end
217
223
 
218
- def _quote(value)
224
+ # In Rails 7 used constant TYPE_MAP, we need redefine method
225
+ def type_map
226
+ @type_map ||= Type::TypeMap.new.tap { |m| ClickhouseAdapter.initialize_type_map(m) }
227
+ end
228
+
229
+ def quote(value)
219
230
  case value
220
231
  when Array
221
- '[' + value.map { |v| _quote(v) }.join(', ') + ']'
232
+ '[' + value.map { |v| quote(v) }.join(', ') + ']'
222
233
  else
223
234
  super
224
235
  end
@@ -334,7 +345,13 @@ module ActiveRecord
334
345
  end
335
346
 
336
347
  def drop_table(table_name, options = {}) # :nodoc:
337
- do_execute apply_cluster "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
348
+ query = "DROP TABLE"
349
+ query = "#{query} IF EXISTS " if options[:if_exists]
350
+ query = "#{query} #{quote_table_name(table_name)}"
351
+ query = apply_cluster(query)
352
+ query = "#{query} SYNC" if options[:sync]
353
+
354
+ do_execute(query)
338
355
 
339
356
  if options[:with_distributed]
340
357
  distributed_table_name = options.delete(:with_distributed)
@@ -15,7 +15,7 @@ module Arel
15
15
 
16
16
  def visit_Arel_Table o, collector
17
17
  collector = super
18
- collector << ' FINAL ' if o.final
18
+ collector << ' FINAL' if o.final
19
19
  collector
20
20
  end
21
21
 
@@ -3,107 +3,96 @@ require 'active_record/migration'
3
3
  module ClickhouseActiverecord
4
4
 
5
5
  class SchemaMigration < ::ActiveRecord::SchemaMigration
6
- class << self
6
+ def create_table
7
+ return if table_exists?
7
8
 
8
- def create_table
9
- return if table_exists?
9
+ version_options = connection.internal_string_options_for_primary_key
10
+ table_options = {
11
+ id: false, options: 'ReplacingMergeTree(ver) ORDER BY (version)', if_not_exists: true
12
+ }
13
+ full_config = connection.instance_variable_get(:@full_config) || {}
10
14
 
11
- version_options = connection.internal_string_options_for_primary_key
12
- table_options = {
13
- id: false, options: 'ReplacingMergeTree(ver) ORDER BY (version)', if_not_exists: true
14
- }
15
- full_config = connection.instance_variable_get(:@full_config) || {}
15
+ if full_config[:distributed_service_tables]
16
+ table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(version)')
16
17
 
17
- if full_config[:distributed_service_tables]
18
- table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(version)')
19
-
20
- distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
21
- end
22
-
23
- connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
24
- t.string :version, **version_options
25
- t.column :active, 'Int8', null: false, default: '1'
26
- t.datetime :ver, null: false, default: -> { 'now()' }
27
- end
18
+ distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
28
19
  end
29
20
 
30
- def all_versions
31
- final.where(active: 1).order(:version).pluck(:version)
21
+ connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
22
+ t.string :version, **version_options
23
+ t.column :active, 'Int8', null: false, default: '1'
24
+ t.datetime :ver, null: false, default: -> { 'now()' }
32
25
  end
33
26
  end
34
- end
35
27
 
36
- class InternalMetadata < ::ActiveRecord::InternalMetadata
37
- class << self
28
+ def versions
29
+ table = arel_table.dup
30
+ table.final = true
31
+ sm = Arel::SelectManager.new(table)
32
+ sm.project(arel_table[primary_key])
33
+ sm.order(arel_table[primary_key].asc)
34
+ sm.where([arel_table['active'].eq(1)])
38
35
 
39
- def []=(key, value)
40
- row = final.find_by(key: key)
41
- if row.nil? || row.value != value
42
- create!(key: key, value: value)
43
- end
44
- end
36
+ connection.select_values(sm, "#{self.class} Load")
37
+ end
45
38
 
46
- def [](key)
47
- final.where(key: key).pluck(:value).first
48
- end
39
+ def delete_version(version)
40
+ im = Arel::InsertManager.new(arel_table)
41
+ im.insert(arel_table[primary_key] => version.to_s, arel_table['active'] => 0)
42
+ connection.insert(im, "#{self.class} Create Rollback Version", primary_key, version)
43
+ end
44
+ end
49
45
 
50
- def create_table
51
- return if table_exists?
46
+ class InternalMetadata < ::ActiveRecord::InternalMetadata
52
47
 
53
- key_options = connection.internal_string_options_for_primary_key
54
- table_options = {
55
- id: false,
56
- options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
57
- if_not_exists: true
58
- }
59
- full_config = connection.instance_variable_get(:@full_config) || {}
48
+ def create_table
49
+ return if table_exists? || !enabled?
60
50
 
61
- if full_config[:distributed_service_tables]
62
- table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
51
+ key_options = connection.internal_string_options_for_primary_key
52
+ table_options = {
53
+ id: false,
54
+ options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
55
+ if_not_exists: true
56
+ }
57
+ full_config = connection.instance_variable_get(:@full_config) || {}
63
58
 
64
- distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
65
- end
59
+ if full_config[:distributed_service_tables]
60
+ table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
66
61
 
67
- connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
68
- t.string :key, **key_options
69
- t.string :value
70
- t.timestamps
71
- end
62
+ distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
72
63
  end
73
- end
74
- end
75
64
 
76
- class MigrationContext < ::ActiveRecord::MigrationContext #:nodoc:
77
- attr_reader :migrations_paths, :schema_migration
78
-
79
- def initialize(migrations_paths, schema_migration)
80
- @migrations_paths = migrations_paths
81
- @schema_migration = schema_migration
65
+ connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
66
+ t.string :key, **key_options
67
+ t.string :value
68
+ t.timestamps
69
+ end
82
70
  end
83
71
 
84
- def up(target_version = nil)
85
- selected_migrations = if block_given?
86
- migrations.select { |m| yield m }
87
- else
88
- migrations
89
- end
72
+ private
90
73
 
91
- ClickhouseActiverecord::Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
74
+ def update_entry(key, new_value)
75
+ create_entry(key, new_value)
92
76
  end
93
77
 
94
- def down(target_version = nil)
95
- selected_migrations = if block_given?
96
- migrations.select { |m| yield m }
97
- else
98
- migrations
99
- end
78
+ def select_entry(key)
79
+ table = arel_table.dup
80
+ table.final = true
81
+ sm = Arel::SelectManager.new(table)
82
+ sm.project(Arel::Nodes::SqlLiteral.new("*"))
83
+ sm.where(table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
84
+ sm.order(table[primary_key].asc)
85
+ sm.limit = 1
100
86
 
101
- ClickhouseActiverecord::Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
87
+ connection.select_all(sm, "#{self.class} Load").first
102
88
  end
89
+ end
90
+
91
+ class MigrationContext < ::ActiveRecord::MigrationContext #:nodoc:
103
92
 
104
93
  def get_all_versions
105
94
  if schema_migration.table_exists?
106
- schema_migration.all_versions.map(&:to_i)
95
+ schema_migration.versions.map(&:to_i)
107
96
  else
108
97
  []
109
98
  end
@@ -111,36 +100,4 @@ module ClickhouseActiverecord
111
100
 
112
101
  end
113
102
 
114
- class Migrator < ::ActiveRecord::Migrator
115
-
116
- def initialize(direction, migrations, schema_migration, target_version = nil)
117
- @direction = direction
118
- @target_version = target_version
119
- @migrated_versions = nil
120
- @migrations = migrations
121
- @schema_migration = schema_migration
122
-
123
- validate(@migrations)
124
-
125
- @schema_migration.create_table
126
- ClickhouseActiverecord::InternalMetadata.create_table
127
- end
128
-
129
- def record_version_state_after_migrating(version)
130
- if down?
131
- migrated.delete(version)
132
- @schema_migration.create!(version: version.to_s, active: 0)
133
- else
134
- super
135
- end
136
- end
137
-
138
- private
139
-
140
- def record_environment
141
- return if down?
142
- ClickhouseActiverecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
143
- end
144
-
145
- end
146
103
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module ClickhouseActiverecord
4
4
  class Tasks
5
-
6
5
  delegate :connection, :establish_connection, :clear_active_connections!, to: ActiveRecord::Base
7
6
 
8
7
  def initialize(configuration)
@@ -11,7 +10,7 @@ module ClickhouseActiverecord
11
10
 
12
11
  def create
13
12
  establish_master_connection
14
- connection.create_database @configuration["database"]
13
+ connection.create_database @configuration['database']
15
14
  rescue ActiveRecord::StatementInvalid => e
16
15
  if e.cause.to_s.include?('already exists')
17
16
  raise ActiveRecord::DatabaseAlreadyExists
@@ -22,7 +21,7 @@ module ClickhouseActiverecord
22
21
 
23
22
  def drop
24
23
  establish_master_connection
25
- connection.drop_database @configuration["database"]
24
+ connection.drop_database @configuration['database']
26
25
  end
27
26
 
28
27
  def purge
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '0.6.1'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -11,21 +11,65 @@ module CoreExtensions
11
11
  self
12
12
  end
13
13
 
14
+ # Define settings in the SETTINGS clause of the SELECT query. The setting value is applied only to that query and is reset to the default or previous value after the query is executed.
15
+ # For example:
16
+ #
17
+ # users = User.settings(optimize_read_in_order: 1, cast_keep_nullable: 1).where(name: 'John')
18
+ # # SELECT users.* FROM users WHERE users.name = 'John' SETTINGS optimize_read_in_order = 1, cast_keep_nullable = 1
19
+ #
20
+ # An <tt>ActiveRecord::ActiveRecordError</tt> will be raised if database not ClickHouse.
14
21
  # @param [Hash] opts
15
22
  def settings(**opts)
23
+ spawn.settings!(**opts)
24
+ end
25
+
26
+ # @param [Hash] opts
27
+ def settings!(**opts)
28
+ assert_mutability!
16
29
  check_command('SETTINGS')
17
30
  @values[:settings] = (@values[:settings] || {}).merge opts
18
31
  self
19
32
  end
20
33
 
34
+ # When FINAL is specified, ClickHouse fully merges the data before returning the result and thus performs all data transformations that happen during merges for the given table engine.
35
+ # For example:
36
+ #
37
+ # users = User.final.all
38
+ # # SELECT users.* FROM users FINAL
39
+ #
40
+ # An <tt>ActiveRecord::ActiveRecordError</tt> will be raised if database not ClickHouse.
21
41
  # @param [Boolean] final
22
42
  def final(final = true)
43
+ spawn.final!(final)
44
+ end
45
+
46
+ # @param [Boolean] final
47
+ def final!(final = true)
48
+ assert_mutability!
23
49
  check_command('FINAL')
24
50
  @table = @table.dup
25
51
  @table.final = final
26
52
  self
27
53
  end
28
54
 
55
+ # The USING clause specifies one or more columns to join, which establishes the equality of these columns. For example:
56
+ #
57
+ # users = User.joins(:joins).using(:event_name, :date)
58
+ # # SELECT users.* FROM users INNER JOIN joins USING event_name,date
59
+ #
60
+ # An <tt>ActiveRecord::ActiveRecordError</tt> will be raised if database not ClickHouse.
61
+ # @param [Array] opts
62
+ def using(*opts)
63
+ spawn.using!(*opts)
64
+ end
65
+
66
+ # @param [Array] opts
67
+ def using!(*opts)
68
+ assert_mutability!
69
+ @values[:using] = opts
70
+ self
71
+ end
72
+
29
73
  private
30
74
 
31
75
  def check_command(cmd)
@@ -36,6 +80,7 @@ module CoreExtensions
36
80
  arel = super
37
81
 
38
82
  arel.settings(@values[:settings]) if @values[:settings].present?
83
+ arel.using(@values[:using]) if @values[:using].present?
39
84
 
40
85
  arel
41
86
  end
@@ -4,7 +4,7 @@ module CoreExtensions
4
4
  module SelectStatement
5
5
  attr_accessor :settings
6
6
 
7
- def initialize
7
+ def initialize(relation = nil)
8
8
  super
9
9
  @settings = nil
10
10
  end
@@ -1,86 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :clickhouse do
4
-
5
4
  task prepare_schema_migration_table: :environment do
6
- ClickhouseActiverecord::SchemaMigration.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
5
+ connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection
6
+ connection.schema_migration.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
7
7
  end
8
8
 
9
9
  task prepare_internal_metadata_table: :environment do
10
- ClickhouseActiverecord::InternalMetadata.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
11
- end
12
-
13
- task load_config: :environment do
14
- ENV['SCHEMA'] = "db/clickhouse_schema.rb"
15
- ActiveRecord::Migrator.migrations_paths = ["db/migrate_clickhouse"]
16
- ActiveRecord::Base.establish_connection(:"#{Rails.env}_clickhouse")
10
+ connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection
11
+ connection.internal_metadata.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
17
12
  end
18
13
 
19
14
  namespace :schema do
20
-
21
- # todo not testing
15
+ # TODO: not testing
22
16
  desc 'Load database schema'
23
- task load: [:load_config, :prepare_internal_metadata_table] do |t, args|
24
- simple = ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
17
+ task load: %i[load_config prepare_internal_metadata_table] do
18
+ simple = ENV['simple'] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
25
19
  ClickhouseActiverecord::SchemaMigration.drop_table
26
- load("#{Rails.root}/db/clickhouse_schema#{simple}.rb")
20
+ load(Rails.root.join("db/clickhouse_schema#{simple}.rb"))
27
21
  end
28
22
 
29
23
  desc 'Dump database schema'
30
- task dump: :environment do |t, args|
31
- simple = ENV['simple'] || args[:simple] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
32
- filename = "#{Rails.root}/db/clickhouse_schema#{simple}.rb"
24
+ task dump: :environment do |_, args|
25
+ simple = ENV['simple'] || args[:simple] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
26
+ filename = Rails.root.join("db/clickhouse_schema#{simple}.rb")
33
27
  File.open(filename, 'w:utf-8') do |file|
34
- ActiveRecord::Base.establish_connection(:"#{Rails.env}_clickhouse")
35
- ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, !!simple)
28
+ ActiveRecord::Base.establish_connection(:clickhouse)
29
+ ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, simple.present?)
36
30
  end
37
31
  end
38
-
39
32
  end
40
33
 
41
34
  namespace :structure do
42
35
  desc 'Load database structure'
43
- task load: [:load_config, 'db:check_protected_environments'] do
44
- ClickhouseActiverecord::Tasks.new(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"]).structure_load("#{Rails.root}/db/clickhouse_structure.sql")
36
+ task load: ['db:check_protected_environments'] do
37
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
38
+ ClickhouseActiverecord::Tasks.new(config).structure_load(Rails.root.join('db/clickhouse_structure.sql'))
45
39
  end
46
40
 
47
41
  desc 'Dump database structure'
48
- task dump: [:load_config, 'db:check_protected_environments'] do
49
- ClickhouseActiverecord::Tasks.new(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"]).structure_dump("#{Rails.root}/db/clickhouse_structure.sql")
42
+ task dump: ['db:check_protected_environments'] do
43
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
44
+ ClickhouseActiverecord::Tasks.new(config).structure_dump(Rails.root.join('db/clickhouse_structure.sql'))
50
45
  end
51
46
  end
52
47
 
53
48
  desc 'Creates the database from DATABASE_URL or config/database.yml'
54
- task create: [:load_config] do
55
- ActiveRecord::Tasks::DatabaseTasks.create(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
49
+ task create: [] do
50
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
51
+ ActiveRecord::Tasks::DatabaseTasks.create(config)
56
52
  end
57
53
 
58
54
  desc 'Drops the database from DATABASE_URL or config/database.yml'
59
- task drop: [:load_config, 'db:check_protected_environments'] do
60
- ActiveRecord::Tasks::DatabaseTasks.drop(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
55
+ task drop: ['db:check_protected_environments'] do
56
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
57
+ ActiveRecord::Tasks::DatabaseTasks.drop(config)
61
58
  end
62
59
 
63
60
  desc 'Empty the database from DATABASE_URL or config/database.yml'
64
- task purge: [:load_config, 'db:check_protected_environments'] do
65
- ActiveRecord::Tasks::DatabaseTasks.purge(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
61
+ task purge: ['db:check_protected_environments'] do
62
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
63
+ ActiveRecord::Tasks::DatabaseTasks.purge(config)
66
64
  end
67
65
 
68
66
  # desc 'Resets your database using your migrations for the current environment'
69
- task reset: :load_config do
67
+ task :reset do
70
68
  Rake::Task['clickhouse:purge'].execute
71
69
  Rake::Task['clickhouse:migrate'].execute
72
70
  end
73
71
 
74
72
  desc 'Migrate the clickhouse database'
75
- task migrate: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
76
- Rake::Task['db:migrate'].execute
73
+ task migrate: %i[prepare_schema_migration_table prepare_internal_metadata_table] do
74
+ Rake::Task['db:migrate:clickhouse'].execute
77
75
  if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
78
76
  Rake::Task['clickhouse:schema:dump'].execute(simple: true)
79
77
  end
80
78
  end
81
79
 
82
80
  desc 'Rollback the clickhouse database'
83
- task rollback: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
84
- Rake::Task['db:rollback'].execute
81
+ task rollback: %i[prepare_schema_migration_table prepare_internal_metadata_table] do
82
+ Rake::Task['db:rollback:clickhouse'].execute
83
+ if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
84
+ Rake::Task['clickhouse:schema:dump'].execute(simple: true)
85
+ end
85
86
  end
86
87
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clickhouse-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Odintsov
@@ -30,20 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '5.2'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '7'
33
+ version: '7.1'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
- version: '5.2'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '7'
40
+ version: '7.1'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rake
49
43
  requirement: !ruby/object:Gem::Requirement