clickhouse-activerecord 0.5.3 → 0.5.10
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 +4 -4
- data/CHANGELOG.md +10 -1
- data/README.md +1 -0
- data/clickhouse-activerecord.gemspec +1 -1
- data/core_extensions/active_record/migration/command_recorder.rb +18 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +46 -3
- data/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +36 -2
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +6 -4
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +93 -24
- data/lib/clickhouse-activerecord/migration.rb +48 -16
- data/lib/clickhouse-activerecord/tasks.rb +10 -2
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/clickhouse-activerecord.rb +3 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e172255ef4610b0db49f84dea36cdd85951fc4ea596f1cbe7b63c62e9c9b2633
|
4
|
+
data.tar.gz: ec85d89e0274006250466bd8a96654d30eb6614512c4d0d0bee80dcf08285a20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5130aef7dc279582f73ca91fbd0be7f80cf7200d241b17ab747e356762a9a21d5060f2f10995762a29ba5c46942bda17643937a7f849b28da58214998e85462d
|
7
|
+
data.tar.gz: '00096694c1b25b72e1f45c1f0160714eceb0c1f0c101206a9e4bc0cb4abadc825c24665f68ad4bcc7950b07553ffbceaa809830df5b35c7c3043ae9acb969ce6'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
+
### Version 0.5.8 (Oct 25, 2021)
|
2
|
+
|
3
|
+
* Added support rails 7
|
4
|
+
|
5
|
+
### Version 0.5.6 (Oct 25, 2021)
|
6
|
+
|
7
|
+
* Added auto creating service distributed tables and additional options for creating view [@ygreeek](https://github.com/ygreeek)
|
8
|
+
* Added default user agent
|
9
|
+
|
1
10
|
### Version 0.5.3 (Sep 22, 2021)
|
2
|
-
|
11
|
+
|
3
12
|
* Fix replica cluster for a new syntax MergeTree
|
4
13
|
* Fix support rails 5.2 on alter table
|
5
14
|
* Support array type of column
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_runtime_dependency 'bundler', '>= 1.13.4'
|
27
27
|
spec.add_runtime_dependency 'activerecord', '>= 5.2'
|
28
28
|
|
29
|
-
spec.add_development_dependency 'bundler', '
|
29
|
+
spec.add_development_dependency 'bundler', '>= 1.15'
|
30
30
|
spec.add_development_dependency 'rake', '~> 13.0'
|
31
31
|
spec.add_development_dependency 'rspec', '~> 3.4'
|
32
32
|
spec.add_development_dependency 'pry', '~> 0.12'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CoreExtensions
|
2
|
+
module ActiveRecord
|
3
|
+
module Migration
|
4
|
+
module CommandRecorder
|
5
|
+
def create_view(*args, &block)
|
6
|
+
record(:create_view, args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def invert_create_view(args)
|
12
|
+
view_name, options = args
|
13
|
+
[:drop_table, view_name, options]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -19,9 +19,18 @@ module ActiveRecord
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def add_column_options!(sql, options)
|
22
|
+
if options[:value]
|
23
|
+
sql.gsub!(/\s+(.*)/, " \\1(#{options[:value]})")
|
24
|
+
end
|
25
|
+
if options[:fixed_string]
|
26
|
+
sql.gsub!(/\s+(.*)/, " FixedString(#{options[:fixed_string]})")
|
27
|
+
end
|
22
28
|
if options[:null] || options[:null].nil?
|
23
29
|
sql.gsub!(/\s+(.*)/, ' Nullable(\1)')
|
24
30
|
end
|
31
|
+
if options[:low_cardinality]
|
32
|
+
sql.gsub!(/\s+(.*)/, ' LowCardinality(\1)')
|
33
|
+
end
|
25
34
|
if options[:array]
|
26
35
|
sql.gsub!(/\s+(.*)/, ' Array(\1)')
|
27
36
|
end
|
@@ -46,17 +55,48 @@ module ActiveRecord
|
|
46
55
|
create_sql
|
47
56
|
end
|
48
57
|
|
58
|
+
def add_as_clause!(create_sql, options)
|
59
|
+
return unless options.as
|
60
|
+
|
61
|
+
assign_database_to_subquery!(options.as) if options.view
|
62
|
+
create_sql << " AS #{to_sql(options.as)}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def assign_database_to_subquery!(subquery)
|
66
|
+
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
|
67
|
+
return unless subquery
|
68
|
+
|
69
|
+
match = subquery.match(/(?<=from)[^.\w]+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
|
70
|
+
return unless match
|
71
|
+
return if match[:database]
|
72
|
+
|
73
|
+
subquery[match.begin(:table_name)...match.end(:table_name)] =
|
74
|
+
"#{current_database}.#{match[:table_name].sub('.', '')}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_to_clause!(create_sql, options)
|
78
|
+
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
|
79
|
+
return unless options.to
|
80
|
+
|
81
|
+
match = options.to.match(/(?<database>.+(?=\.))?(?<table_name>.+)/i)
|
82
|
+
return unless match
|
83
|
+
return if match[:database]
|
84
|
+
|
85
|
+
create_sql << "TO #{current_database}.#{match[:table_name].sub('.', '')}"
|
86
|
+
end
|
87
|
+
|
49
88
|
def visit_TableDefinition(o)
|
50
89
|
create_sql = +"CREATE#{table_modifier_in_create(o)} #{o.view ? "VIEW" : "TABLE"} "
|
51
90
|
create_sql << "IF NOT EXISTS " if o.if_not_exists
|
52
91
|
create_sql << "#{quote_table_name(o.name)} "
|
92
|
+
add_to_clause!(create_sql, o) if o.materialized
|
53
93
|
|
54
94
|
statements = o.columns.map { |c| accept c }
|
55
95
|
statements << accept(o.primary_keys) if o.primary_keys
|
56
96
|
create_sql << "(#{statements.join(', ')})" if statements.present?
|
57
|
-
# Attach options for only table or materialized view
|
58
|
-
add_table_options!(create_sql, o)
|
59
|
-
create_sql
|
97
|
+
# Attach options for only table or materialized view without TO section
|
98
|
+
add_table_options!(create_sql, o) if !o.view || o.view && o.materialized && !o.to
|
99
|
+
add_as_clause!(create_sql, o)
|
60
100
|
create_sql
|
61
101
|
end
|
62
102
|
|
@@ -84,6 +124,9 @@ module ActiveRecord
|
|
84
124
|
change_column_sql
|
85
125
|
end
|
86
126
|
|
127
|
+
def current_database
|
128
|
+
ActiveRecord::Base.connection_db_config.database
|
129
|
+
end
|
87
130
|
end
|
88
131
|
end
|
89
132
|
end
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
module Clickhouse
|
6
6
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
7
7
|
|
8
|
-
attr_reader :view, :materialized, :if_not_exists
|
8
|
+
attr_reader :view, :materialized, :if_not_exists, :to
|
9
9
|
|
10
10
|
def initialize(
|
11
11
|
conn,
|
@@ -17,6 +17,7 @@ module ActiveRecord
|
|
17
17
|
comment: nil,
|
18
18
|
view: false,
|
19
19
|
materialized: false,
|
20
|
+
to: nil,
|
20
21
|
**
|
21
22
|
)
|
22
23
|
@conn = conn
|
@@ -32,6 +33,7 @@ module ActiveRecord
|
|
32
33
|
@comment = comment
|
33
34
|
@view = view || materialized
|
34
35
|
@materialized = materialized
|
36
|
+
@to = to
|
35
37
|
end
|
36
38
|
|
37
39
|
def integer(*args, **options)
|
@@ -58,7 +60,39 @@ module ActiveRecord
|
|
58
60
|
kind = :int256 if options[:limit] > 16
|
59
61
|
end
|
60
62
|
end
|
61
|
-
args.each { |name| column(name, kind, options.except(:limit, :unsigned)) }
|
63
|
+
args.each { |name| column(name, kind, **options.except(:limit, :unsigned)) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def datetime(*args, **options)
|
67
|
+
kind = :datetime
|
68
|
+
|
69
|
+
if options[:precision]
|
70
|
+
kind = :datetime64
|
71
|
+
options[:value] = options[:precision]
|
72
|
+
end
|
73
|
+
|
74
|
+
args.each { |name| column(name, kind, **options.except(:precision)) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def uuid(*args, **options)
|
78
|
+
args.each { |name| column(name, :uuid, **options) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def enum(*args, **options)
|
82
|
+
kind = :enum8
|
83
|
+
|
84
|
+
unless options[:value].is_a? Hash
|
85
|
+
raise ArgumentError, "Column #{args.first}: option 'value' must be Hash, got: #{options[:value].class}"
|
86
|
+
end
|
87
|
+
|
88
|
+
options[:value] = options[:value].each_with_object([]) { |(k, v), arr| arr.push("'#{k}' = #{v}") }.join(', ')
|
89
|
+
|
90
|
+
if options[:limit]
|
91
|
+
kind = :enum8 if options[:limit] == 1
|
92
|
+
kind = :enum16 if options[:limit] == 2
|
93
|
+
end
|
94
|
+
|
95
|
+
args.each { |name| column(name, kind, **options.except(:limit)) }
|
62
96
|
end
|
63
97
|
end
|
64
98
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'clickhouse-activerecord/version'
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module ConnectionAdapters
|
5
7
|
module Clickhouse
|
@@ -37,7 +39,7 @@ module ActiveRecord
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def tables(name = nil)
|
40
|
-
result = do_system_execute(
|
42
|
+
result = do_system_execute("SHOW TABLES WHERE name NOT LIKE '.inner_id.%'", name)
|
41
43
|
return [] if result.nil?
|
42
44
|
result['data'].flatten
|
43
45
|
end
|
@@ -58,7 +60,7 @@ module ActiveRecord
|
|
58
60
|
|
59
61
|
def do_system_execute(sql, name = nil)
|
60
62
|
log_with_debug(sql, "#{adapter_name} #{name}") do
|
61
|
-
res = @connection.post("/?#{@config.to_param}", "#{sql} FORMAT JSONCompact")
|
63
|
+
res = @connection.post("/?#{@config.to_param}", "#{sql} FORMAT JSONCompact", 'User-Agent' => "Clickhouse ActiveRecord #{ClickhouseActiverecord::VERSION}")
|
62
64
|
|
63
65
|
process_response(res)
|
64
66
|
end
|
@@ -68,7 +70,7 @@ module ActiveRecord
|
|
68
70
|
log(sql, "#{adapter_name} #{name}") do
|
69
71
|
formatted_sql = apply_format(sql, format)
|
70
72
|
request_params = @config || {}
|
71
|
-
res = @connection.post("/?#{request_params.merge(settings).to_param}", formatted_sql)
|
73
|
+
res = @connection.post("/?#{request_params.merge(settings).to_param}", formatted_sql, 'User-Agent' => "Clickhouse ActiveRecord #{ClickhouseActiverecord::VERSION}")
|
72
74
|
|
73
75
|
process_response(res)
|
74
76
|
end
|
@@ -90,7 +92,7 @@ module ActiveRecord
|
|
90
92
|
if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
|
91
93
|
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
|
92
94
|
end
|
93
|
-
|
95
|
+
do_execute(insert_versions_sql(inserting), nil, settings: {max_partitions_per_insert_block: [100, inserting.size].max})
|
94
96
|
end
|
95
97
|
end
|
96
98
|
|
@@ -32,6 +32,7 @@ module ActiveRecord
|
|
32
32
|
sslca: config[:sslca],
|
33
33
|
read_timeout: config[:read_timeout],
|
34
34
|
write_timeout: config[:write_timeout],
|
35
|
+
keep_alive_timeout: config[:keep_alive_timeout]
|
35
36
|
}
|
36
37
|
end
|
37
38
|
|
@@ -46,20 +47,18 @@ module ActiveRecord
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
# Replace for only ClickhouseAdapter
|
50
|
+
module ClickhouseRelationReverseOrder
|
52
51
|
def reverse_order!
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
52
|
+
return super unless connection.is_a?(ConnectionAdapters::ClickhouseAdapter)
|
53
|
+
|
54
|
+
orders = order_values.uniq.compact_blank
|
55
|
+
return super unless orders.empty? && !primary_key
|
56
|
+
|
57
|
+
self.order_values = %w(date created_at).select {|c| column_names.include?(c) }.map{|c| arel_attribute(c).desc }
|
60
58
|
self
|
61
59
|
end
|
62
60
|
end
|
61
|
+
Relation.prepend(ClickhouseRelationReverseOrder)
|
63
62
|
|
64
63
|
module TypeCaster
|
65
64
|
class Map
|
@@ -86,9 +85,8 @@ module ActiveRecord
|
|
86
85
|
def arel_table # :nodoc:
|
87
86
|
@arel_table ||= ClickhouseActiverecord::Arel::Table.new(table_name, type_caster: type_caster)
|
88
87
|
end
|
89
|
-
|
90
88
|
end
|
91
|
-
|
89
|
+
end
|
92
90
|
|
93
91
|
module ConnectionAdapters
|
94
92
|
class ClickhouseColumn < Column
|
@@ -104,8 +102,13 @@ module ActiveRecord
|
|
104
102
|
float: { name: 'Float32' },
|
105
103
|
decimal: { name: 'Decimal' },
|
106
104
|
datetime: { name: 'DateTime' },
|
105
|
+
datetime64: { name: 'DateTime64' },
|
107
106
|
date: { name: 'Date' },
|
108
107
|
boolean: { name: 'UInt8' },
|
108
|
+
uuid: { name: 'UUID' },
|
109
|
+
|
110
|
+
enum8: { name: 'Enum8' },
|
111
|
+
enum16: { name: 'Enum16' },
|
109
112
|
|
110
113
|
int8: { name: 'Int8' },
|
111
114
|
int16: { name: 'Int16' },
|
@@ -182,6 +185,20 @@ module ActiveRecord
|
|
182
185
|
end
|
183
186
|
end
|
184
187
|
|
188
|
+
# `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
|
189
|
+
# except this permits a space after the comma
|
190
|
+
|
191
|
+
def extract_scale(sql_type)
|
192
|
+
case sql_type
|
193
|
+
when /\((\d+)\)/ then 0
|
194
|
+
when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def extract_precision(sql_type)
|
199
|
+
$1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
|
200
|
+
end
|
201
|
+
|
185
202
|
def initialize_type_map(m) # :nodoc:
|
186
203
|
super
|
187
204
|
register_class_with_limit m, %r(String), Type::String
|
@@ -210,14 +227,22 @@ module ActiveRecord
|
|
210
227
|
# Quoting time without microseconds
|
211
228
|
def quoted_date(value)
|
212
229
|
if value.acts_like?(:time)
|
213
|
-
|
230
|
+
if ActiveRecord::version >= Gem::Version.new('7')
|
231
|
+
zone_conversion_method = ActiveRecord.default_timezone == :utc ? :getutc : :getlocal
|
232
|
+
else
|
233
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
234
|
+
end
|
214
235
|
|
215
236
|
if value.respond_to?(zone_conversion_method)
|
216
237
|
value = value.send(zone_conversion_method)
|
217
238
|
end
|
218
239
|
end
|
219
240
|
|
220
|
-
|
241
|
+
if ActiveRecord::version >= Gem::Version.new('7')
|
242
|
+
value.to_fs(:db)
|
243
|
+
else
|
244
|
+
value.to_s(:db)
|
245
|
+
end
|
221
246
|
end
|
222
247
|
|
223
248
|
def column_name_for_operation(operation, node) # :nodoc:
|
@@ -262,26 +287,36 @@ module ActiveRecord
|
|
262
287
|
def create_view(table_name, **options)
|
263
288
|
options.merge!(view: true)
|
264
289
|
options = apply_replica(table_name, options)
|
265
|
-
td = create_table_definition(apply_cluster(table_name), options)
|
290
|
+
td = create_table_definition(apply_cluster(table_name), **options)
|
266
291
|
yield td if block_given?
|
267
292
|
|
268
293
|
if options[:force]
|
269
294
|
drop_table(table_name, options.merge(if_exists: true))
|
270
295
|
end
|
271
296
|
|
272
|
-
|
297
|
+
do_execute(schema_creation.accept(td), format: nil)
|
273
298
|
end
|
274
299
|
|
275
|
-
def create_table(table_name, **options)
|
300
|
+
def create_table(table_name, **options, &block)
|
276
301
|
options = apply_replica(table_name, options)
|
277
|
-
td = create_table_definition(apply_cluster(table_name), options)
|
278
|
-
|
302
|
+
td = create_table_definition(apply_cluster(table_name), **options)
|
303
|
+
block.call td if block_given?
|
279
304
|
|
280
305
|
if options[:force]
|
281
306
|
drop_table(table_name, options.merge(if_exists: true))
|
282
307
|
end
|
283
308
|
|
284
|
-
|
309
|
+
do_execute(schema_creation.accept(td), format: nil)
|
310
|
+
|
311
|
+
if options[:with_distributed]
|
312
|
+
distributed_table_name = options.delete(:with_distributed)
|
313
|
+
sharding_key = options.delete(:sharding_key) || 'rand()'
|
314
|
+
raise 'Set a cluster' unless cluster
|
315
|
+
|
316
|
+
distributed_options =
|
317
|
+
"Distributed(#{cluster}, #{@config[:database]}, #{table_name}, #{sharding_key})"
|
318
|
+
create_table(distributed_table_name, **options.merge(options: distributed_options), &block)
|
319
|
+
end
|
285
320
|
end
|
286
321
|
|
287
322
|
# Drops a ClickHouse database.
|
@@ -299,6 +334,11 @@ module ActiveRecord
|
|
299
334
|
|
300
335
|
def drop_table(table_name, options = {}) # :nodoc:
|
301
336
|
do_execute apply_cluster "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
|
337
|
+
|
338
|
+
if options[:with_distributed]
|
339
|
+
distributed_table_name = options.delete(:with_distributed)
|
340
|
+
drop_table(distributed_table_name, **options)
|
341
|
+
end
|
302
342
|
end
|
303
343
|
|
304
344
|
def change_column(table_name, column_name, type, options = {})
|
@@ -324,10 +364,24 @@ module ActiveRecord
|
|
324
364
|
@full_config[:replica_name]
|
325
365
|
end
|
326
366
|
|
367
|
+
def use_default_replicated_merge_tree_params?
|
368
|
+
database_engine_atomic? && @full_config[:use_default_replicated_merge_tree_params]
|
369
|
+
end
|
370
|
+
|
371
|
+
def use_replica?
|
372
|
+
(replica || use_default_replicated_merge_tree_params?) && cluster
|
373
|
+
end
|
374
|
+
|
327
375
|
def replica_path(table)
|
328
376
|
"/clickhouse/tables/#{cluster}/#{@config[:database]}.#{table}"
|
329
377
|
end
|
330
378
|
|
379
|
+
def database_engine_atomic?
|
380
|
+
current_database_engine = "select engine from system.databases where name = '#{@config[:database]}'"
|
381
|
+
res = select_one(current_database_engine)
|
382
|
+
res['engine'] == 'Atomic' if res
|
383
|
+
end
|
384
|
+
|
331
385
|
def apply_cluster(sql)
|
332
386
|
cluster ? "#{sql} ON CLUSTER #{cluster}" : sql
|
333
387
|
end
|
@@ -366,18 +420,33 @@ module ActiveRecord
|
|
366
420
|
@connection.read_timeout = @connection_parameters[:read_timeout] if @connection_parameters[:read_timeout]
|
367
421
|
@connection.write_timeout = @connection_parameters[:write_timeout] if @connection_parameters[:write_timeout]
|
368
422
|
|
423
|
+
# Use clickhouse default keep_alive_timeout value of 10, rather than Net::HTTP's default of 2
|
424
|
+
@connection.keep_alive_timeout = @connection_parameters[:keep_alive_timeout] || 10
|
425
|
+
|
369
426
|
@connection
|
370
427
|
end
|
371
428
|
|
372
429
|
def apply_replica(table, options)
|
373
|
-
if
|
374
|
-
|
375
|
-
|
376
|
-
options[:options] = "Replicated#{match[1]}(#{([replica_path(table), replica].map{|v| "'#{v}'"} + [match[2].presence]).compact.join(', ')})#{match[3]}"
|
430
|
+
if use_replica? && options[:options]
|
431
|
+
if options[:options].match(/^Replicated/)
|
432
|
+
raise 'Do not try create Replicated table. It will be configured based on the *MergeTree engine.'
|
377
433
|
end
|
434
|
+
|
435
|
+
options[:options] = configure_replica(table, options[:options])
|
378
436
|
end
|
379
437
|
options
|
380
438
|
end
|
439
|
+
|
440
|
+
def configure_replica(table, options)
|
441
|
+
match = options.match(/^(.*?MergeTree)(?:\(([^\)]*)\))?((?:.|\n)*)/)
|
442
|
+
return options unless match
|
443
|
+
|
444
|
+
if replica
|
445
|
+
engine_params = ([replica_path(table), replica].map { |v| "'#{v}'" } + [match[2].presence]).compact.join(', ')
|
446
|
+
end
|
447
|
+
|
448
|
+
"Replicated#{match[1]}(#{engine_params})#{match[3]}"
|
449
|
+
end
|
381
450
|
end
|
382
451
|
end
|
383
452
|
end
|
@@ -6,14 +6,24 @@ module ClickhouseActiverecord
|
|
6
6
|
class << self
|
7
7
|
|
8
8
|
def create_table
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
return if table_exists?
|
10
|
+
|
11
|
+
version_options = connection.internal_string_options_for_primary_key
|
12
|
+
table_options = {
|
13
|
+
id: false, options: 'ReplacingMergeTree(ver) PARTITION BY version ORDER BY (version)', if_not_exists: true
|
14
|
+
}
|
15
|
+
full_config = connection.instance_variable_get(:@full_config) || {}
|
16
|
+
|
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()' }
|
17
27
|
end
|
18
28
|
end
|
19
29
|
|
@@ -26,14 +36,26 @@ module ClickhouseActiverecord
|
|
26
36
|
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
27
37
|
class << self
|
28
38
|
def create_table
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
return if table_exists?
|
40
|
+
|
41
|
+
key_options = connection.internal_string_options_for_primary_key
|
42
|
+
table_options = {
|
43
|
+
id: false,
|
44
|
+
options: connection.adapter_name.downcase == 'clickhouse' ? 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)' : '',
|
45
|
+
if_not_exists: true
|
46
|
+
}
|
47
|
+
full_config = connection.instance_variable_get(:@full_config) || {}
|
48
|
+
|
49
|
+
if full_config[:distributed_service_tables]
|
50
|
+
table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
|
51
|
+
|
52
|
+
distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
|
53
|
+
end
|
54
|
+
|
55
|
+
connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
|
56
|
+
t.string :key, **key_options
|
57
|
+
t.string :value
|
58
|
+
t.timestamps
|
37
59
|
end
|
38
60
|
end
|
39
61
|
end
|
@@ -47,6 +69,16 @@ module ClickhouseActiverecord
|
|
47
69
|
@schema_migration = schema_migration
|
48
70
|
end
|
49
71
|
|
72
|
+
def up(target_version = nil)
|
73
|
+
selected_migrations = if block_given?
|
74
|
+
migrations.select { |m| yield m }
|
75
|
+
else
|
76
|
+
migrations
|
77
|
+
end
|
78
|
+
|
79
|
+
ClickhouseActiverecord::Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
|
80
|
+
end
|
81
|
+
|
50
82
|
def down(target_version = nil)
|
51
83
|
selected_migrations = if block_given?
|
52
84
|
migrations.select { |m| yield m }
|
@@ -14,7 +14,7 @@ module ClickhouseActiverecord
|
|
14
14
|
connection.create_database @configuration["database"]
|
15
15
|
rescue ActiveRecord::StatementInvalid => e
|
16
16
|
if e.cause.to_s.include?('already exists')
|
17
|
-
raise ActiveRecord::
|
17
|
+
raise ActiveRecord::DatabaseAlreadyExists
|
18
18
|
else
|
19
19
|
raise
|
20
20
|
end
|
@@ -43,7 +43,15 @@ module ClickhouseActiverecord
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def structure_load(*args)
|
46
|
-
File.read(args.first).split(";\n\n").each
|
46
|
+
File.read(args.first).split(";\n\n").each do |sql|
|
47
|
+
if sql.gsub(/[a-z]/i, '').blank?
|
48
|
+
next
|
49
|
+
elsif sql =~ /^INSERT INTO/
|
50
|
+
connection.do_execute(sql, nil, format: nil)
|
51
|
+
else
|
52
|
+
connection.execute(sql)
|
53
|
+
end
|
54
|
+
end
|
47
55
|
end
|
48
56
|
|
49
57
|
def migrate
|
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'active_record/connection_adapters/clickhouse_adapter'
|
4
4
|
|
5
|
+
require_relative '../core_extensions/active_record/migration/command_recorder'
|
6
|
+
ActiveRecord::Migration::CommandRecorder.include CoreExtensions::ActiveRecord::Migration::CommandRecorder
|
7
|
+
|
5
8
|
if defined?(Rails::Railtie)
|
6
9
|
require 'clickhouse-activerecord/railtie'
|
7
10
|
require 'clickhouse-activerecord/schema'
|
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: 0.5.
|
4
|
+
version: 0.5.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Odintsov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -42,14 +42,14 @@ dependencies:
|
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '1.15'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.15'
|
55
55
|
- !ruby/object:Gem::Dependency
|
@@ -112,6 +112,7 @@ files:
|
|
112
112
|
- bin/console
|
113
113
|
- bin/setup
|
114
114
|
- clickhouse-activerecord.gemspec
|
115
|
+
- core_extensions/active_record/migration/command_recorder.rb
|
115
116
|
- lib/active_record/connection_adapters/clickhouse/oid/array.rb
|
116
117
|
- lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb
|
117
118
|
- lib/active_record/connection_adapters/clickhouse/oid/date.rb
|