clickhouse-activerecord 0.5.3 → 0.5.7
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 +5 -0
- data/README.md +1 -0
- data/core_extensions/active_record/migration/command_recorder.rb +27 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +37 -3
- data/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +4 -2
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +4 -2
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +47 -8
- data/lib/clickhouse-activerecord/migration.rb +46 -16
- data/lib/clickhouse-activerecord/tasks.rb +9 -1
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/clickhouse-activerecord.rb +3 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79b43575e5eef93daa11e1a2aca2984f696cbef59021f55d1470b2dc0a5d1e3d
|
4
|
+
data.tar.gz: b79cccb85d8a07fa4dc533d7b7e9042eeaa2afead26214c075b3131e1843394b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 840b01cc1d5b88eee5e031fada23cfd35f7a8d948a35a767b2f5baf1596af665f28c56778e699960511004367b16ee3130be1f5bd2ee713a9a0d5053a9b58a37
|
7
|
+
data.tar.gz: 4e613a19c51b362a05634da06d9528809ed09f01ebec4eac9984656156e9b877bdc4ce321e24bea63ae4e49d85609fa9dda781db60159e60b6d6ee06e8a99a3e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
### Version 0.5.6 (Oct 25, 2021)
|
2
|
+
|
3
|
+
* Added auto creating service distributed tables and additional options for creating view [@ygreeek](https://github.com/ygreeek)
|
4
|
+
* Added default user agent
|
5
|
+
|
1
6
|
### Version 0.5.3 (Sep 22, 2021)
|
2
7
|
|
3
8
|
* Fix replica cluster for a new syntax MergeTree
|
data/README.md
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
module CoreExtensions
|
2
|
+
module ActiveRecord
|
3
|
+
module Migration
|
4
|
+
module CommandRecorder
|
5
|
+
def create_table_with_distributed(*args, &block)
|
6
|
+
record(:create_table_with_distributed, args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_view(*args, &block)
|
10
|
+
record(:create_view, args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def invert_create_table_with_distributed(args)
|
16
|
+
table_name, options = args
|
17
|
+
[:drop_table_with_distributed, table_name, options]
|
18
|
+
end
|
19
|
+
|
20
|
+
def invert_create_view(args)
|
21
|
+
view_name, options = args
|
22
|
+
[:drop_table, view_name, options]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -46,17 +46,48 @@ module ActiveRecord
|
|
46
46
|
create_sql
|
47
47
|
end
|
48
48
|
|
49
|
+
def add_as_clause!(create_sql, options)
|
50
|
+
return unless options.as
|
51
|
+
|
52
|
+
assign_database_to_subquery!(options.as) if options.view
|
53
|
+
create_sql << " AS #{to_sql(options.as)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def assign_database_to_subquery!(subquery)
|
57
|
+
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
|
58
|
+
return unless subquery
|
59
|
+
|
60
|
+
match = subquery.match(/(?<=from)[^.\w]+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
|
61
|
+
return unless match
|
62
|
+
return if match[:database]
|
63
|
+
|
64
|
+
subquery[match.begin(:table_name)...match.end(:table_name)] =
|
65
|
+
"#{current_database}.#{match[:table_name].sub('.', '')}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_to_clause!(create_sql, options)
|
69
|
+
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
|
70
|
+
return unless options.to
|
71
|
+
|
72
|
+
match = options.to.match(/(?<database>.+(?=\.))?(?<table_name>.+)/i)
|
73
|
+
return unless match
|
74
|
+
return if match[:database]
|
75
|
+
|
76
|
+
create_sql << "TO #{current_database}.#{options.to.sub('.', '')} "
|
77
|
+
end
|
78
|
+
|
49
79
|
def visit_TableDefinition(o)
|
50
80
|
create_sql = +"CREATE#{table_modifier_in_create(o)} #{o.view ? "VIEW" : "TABLE"} "
|
51
81
|
create_sql << "IF NOT EXISTS " if o.if_not_exists
|
52
82
|
create_sql << "#{quote_table_name(o.name)} "
|
83
|
+
add_to_clause!(create_sql, o) if o.materialized
|
53
84
|
|
54
85
|
statements = o.columns.map { |c| accept c }
|
55
86
|
statements << accept(o.primary_keys) if o.primary_keys
|
56
87
|
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
|
88
|
+
# Attach options for only table or materialized view without TO section
|
89
|
+
add_table_options!(create_sql, o) if !o.view || o.view && o.materialized && !o.to
|
90
|
+
add_as_clause!(create_sql, o)
|
60
91
|
create_sql
|
61
92
|
end
|
62
93
|
|
@@ -84,6 +115,9 @@ module ActiveRecord
|
|
84
115
|
change_column_sql
|
85
116
|
end
|
86
117
|
|
118
|
+
def current_database
|
119
|
+
ActiveRecord::Base.connection_db_config.database
|
120
|
+
end
|
87
121
|
end
|
88
122
|
end
|
89
123
|
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,7 @@ 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)) }
|
62
64
|
end
|
63
65
|
end
|
64
66
|
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
|
@@ -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
|
@@ -262,7 +262,7 @@ module ActiveRecord
|
|
262
262
|
def create_view(table_name, **options)
|
263
263
|
options.merge!(view: true)
|
264
264
|
options = apply_replica(table_name, options)
|
265
|
-
td = create_table_definition(apply_cluster(table_name), options)
|
265
|
+
td = create_table_definition(apply_cluster(table_name), **options)
|
266
266
|
yield td if block_given?
|
267
267
|
|
268
268
|
if options[:force]
|
@@ -272,10 +272,10 @@ module ActiveRecord
|
|
272
272
|
execute schema_creation.accept td
|
273
273
|
end
|
274
274
|
|
275
|
-
def create_table(table_name, **options)
|
275
|
+
def create_table(table_name, **options, &block)
|
276
276
|
options = apply_replica(table_name, options)
|
277
|
-
td = create_table_definition(apply_cluster(table_name), options)
|
278
|
-
|
277
|
+
td = create_table_definition(apply_cluster(table_name), **options)
|
278
|
+
block.call td if block_given?
|
279
279
|
|
280
280
|
if options[:force]
|
281
281
|
drop_table(table_name, options.merge(if_exists: true))
|
@@ -284,6 +284,19 @@ module ActiveRecord
|
|
284
284
|
execute schema_creation.accept td
|
285
285
|
end
|
286
286
|
|
287
|
+
def create_table_with_distributed(table_name, **options, &block)
|
288
|
+
sharding_key = options.delete(:sharding_key) || 'rand()'
|
289
|
+
create_table("#{table_name}_distributed", **options, &block)
|
290
|
+
raise 'Set a cluster' unless cluster
|
291
|
+
|
292
|
+
distributed_options = "Distributed(#{cluster},#{@config[:database]},#{table_name}_distributed,#{sharding_key})"
|
293
|
+
create_table(table_name, **options.merge(options: distributed_options), &block)
|
294
|
+
end
|
295
|
+
|
296
|
+
def drop_table_with_distributed(table_name, **options)
|
297
|
+
["#{table_name}_distributed", table_name].each { |name| drop_table(name, **options) }
|
298
|
+
end
|
299
|
+
|
287
300
|
# Drops a ClickHouse database.
|
288
301
|
def drop_database(name) #:nodoc:
|
289
302
|
sql = apply_cluster "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
@@ -324,10 +337,24 @@ module ActiveRecord
|
|
324
337
|
@full_config[:replica_name]
|
325
338
|
end
|
326
339
|
|
340
|
+
def use_default_replicated_merge_tree_params?
|
341
|
+
database_engine_atomic? && @full_config[:use_default_replicated_merge_tree_params]
|
342
|
+
end
|
343
|
+
|
344
|
+
def use_replica?
|
345
|
+
(replica || use_default_replicated_merge_tree_params?) && cluster
|
346
|
+
end
|
347
|
+
|
327
348
|
def replica_path(table)
|
328
349
|
"/clickhouse/tables/#{cluster}/#{@config[:database]}.#{table}"
|
329
350
|
end
|
330
351
|
|
352
|
+
def database_engine_atomic?
|
353
|
+
current_database_engine = "select engine from system.databases where name = '#{@config[:database]}'"
|
354
|
+
res = ActiveRecord::Base.connection.select_one(current_database_engine)
|
355
|
+
res['engine'] == 'Atomic' if res
|
356
|
+
end
|
357
|
+
|
331
358
|
def apply_cluster(sql)
|
332
359
|
cluster ? "#{sql} ON CLUSTER #{cluster}" : sql
|
333
360
|
end
|
@@ -370,14 +397,26 @@ module ActiveRecord
|
|
370
397
|
end
|
371
398
|
|
372
399
|
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]}"
|
400
|
+
if use_replica? && options[:options]
|
401
|
+
if options[:options].match(/^Replicated/)
|
402
|
+
raise 'Do not try create Replicated table. It will be configured based on the *MergeTree engine.'
|
377
403
|
end
|
404
|
+
|
405
|
+
options[:options] = configure_replica(table, options[:options])
|
378
406
|
end
|
379
407
|
options
|
380
408
|
end
|
409
|
+
|
410
|
+
def configure_replica(table, options)
|
411
|
+
match = options.match(/^(.*?MergeTree)(?:\(([^\)]*)\))?((?:.|\n)*)/)
|
412
|
+
return options unless match
|
413
|
+
|
414
|
+
if replica
|
415
|
+
engine_params = ([replica_path(table), replica].map { |v| "'#{v}'" } + [match[2].presence]).compact.join(', ')
|
416
|
+
end
|
417
|
+
|
418
|
+
"Replicated#{match[1]}(#{engine_params})#{match[3]}"
|
419
|
+
end
|
381
420
|
end
|
382
421
|
end
|
383
422
|
end
|
@@ -6,14 +6,23 @@ 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
|
+
if connection.instance_variable_get(:@full_config)[:distributed_service_tables]
|
16
|
+
table_options.merge!(sharding_key: 'cityHash64(version)')
|
17
|
+
table_creation_method = 'create_table_with_distributed'
|
18
|
+
else
|
19
|
+
table_creation_method = 'create_table'
|
20
|
+
end
|
21
|
+
|
22
|
+
connection.public_send(table_creation_method, table_name, **table_options) do |t|
|
23
|
+
t.string :version, **version_options
|
24
|
+
t.column :active, 'Int8', null: false, default: '1'
|
25
|
+
t.datetime :ver, null: false, default: -> { 'now()' }
|
17
26
|
end
|
18
27
|
end
|
19
28
|
|
@@ -26,14 +35,25 @@ module ClickhouseActiverecord
|
|
26
35
|
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
27
36
|
class << self
|
28
37
|
def create_table
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
return if table_exists?
|
39
|
+
|
40
|
+
key_options = connection.internal_string_options_for_primary_key
|
41
|
+
table_options = {
|
42
|
+
id: false,
|
43
|
+
options: connection.adapter_name.downcase == 'clickhouse' ? 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)' : '',
|
44
|
+
if_not_exists: true
|
45
|
+
}
|
46
|
+
if connection.instance_variable_get(:@full_config).try(:[], :distributed_service_tables)
|
47
|
+
table_options.merge!(sharding_key: 'cityHash64(created_at)')
|
48
|
+
table_creation_method = 'create_table_with_distributed'
|
49
|
+
else
|
50
|
+
table_creation_method = 'create_table'
|
51
|
+
end
|
52
|
+
|
53
|
+
connection.public_send(table_creation_method, table_name, **table_options) do |t|
|
54
|
+
t.string :key, **key_options
|
55
|
+
t.string :value
|
56
|
+
t.timestamps
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
@@ -47,6 +67,16 @@ module ClickhouseActiverecord
|
|
47
67
|
@schema_migration = schema_migration
|
48
68
|
end
|
49
69
|
|
70
|
+
def up(target_version = nil)
|
71
|
+
selected_migrations = if block_given?
|
72
|
+
migrations.select { |m| yield m }
|
73
|
+
else
|
74
|
+
migrations
|
75
|
+
end
|
76
|
+
|
77
|
+
ClickhouseActiverecord::Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
|
78
|
+
end
|
79
|
+
|
50
80
|
def down(target_version = nil)
|
51
81
|
selected_migrations = if block_given?
|
52
82
|
migrations.select { |m| yield m }
|
@@ -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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Odintsov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -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
|