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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41dd800df06e6269f91b1a39d3c9df2e1e903068b5f13913cd2aad863063685f
4
- data.tar.gz: 50252db6e694467f48669a3c7c52c89e6b1633f08586714db300b8eee59ebbbc
3
+ metadata.gz: 79b43575e5eef93daa11e1a2aca2984f696cbef59021f55d1470b2dc0a5d1e3d
4
+ data.tar.gz: b79cccb85d8a07fa4dc533d7b7e9042eeaa2afead26214c075b3131e1843394b
5
5
  SHA512:
6
- metadata.gz: 539e7dfb17998dd1bcc95ee2c15c6111229775ec29837d8f102291bf5da64db2dcf74ef0f01087443c8c1b1d9902cfdf4a586357508d3d8ed97fa099b1545770
7
- data.tar.gz: f5f8acbf806f9bc8497dcd6aa29c6bfd18b651aae78c28cc0f9449effd4ddce965a1978027d91fa57a998fc1ba0bb0027d33917a3ef35379299185bfde06c784
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
@@ -196,6 +196,7 @@ false`. The default integer is `UInt32`
196
196
  | UInt32 | 0 to 4,294,967,295 | 3,4 |
197
197
  | UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
198
198
  | UInt256 | 0 to ... | 8+ |
199
+ | Array | ... | ... |
199
200
 
200
201
  Example:
201
202
 
@@ -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) if !o.view || o.view && o.materialized
59
- create_sql << " AS #{to_sql(o.as)}" if o.as
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
- yield td if block_given?
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 replica && cluster && options[:options]
374
- match = options[:options].match(/^(.*?MergeTree)(?:\(([^\)]*)\))?(.*?)$/)
375
- if match
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
- unless table_exists?
10
- version_options = connection.internal_string_options_for_primary_key
11
-
12
- connection.create_table(table_name, id: false, options: 'ReplacingMergeTree(ver) PARTITION BY version ORDER BY (version)', if_not_exists: true) do |t|
13
- t.string :version, **version_options
14
- t.column :active, 'Int8', null: false, default: '1'
15
- t.datetime :ver, null: false, default: -> { 'now()' }
16
- end
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
- unless table_exists?
30
- key_options = connection.internal_string_options_for_primary_key
31
-
32
- connection.create_table(table_name, id: false, options: connection.adapter_name.downcase == 'clickhouse' ? 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)' : '', if_not_exists: true) do |t|
33
- t.string :key, key_options
34
- t.string :value
35
- t.timestamps
36
- end
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 { |sql| connection.execute(sql) }
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
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '0.5.3'
2
+ VERSION = '0.5.7'
3
3
  end
@@ -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.3
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-09-22 00:00:00.000000000 Z
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