clickhouse-activerecord 0.5.7 → 0.6.2
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 +8 -2
- data/README.md +14 -2
- data/clickhouse-activerecord.gemspec +1 -2
- data/core_extensions/active_record/migration/command_recorder.rb +0 -9
- data/lib/active_record/connection_adapters/clickhouse/oid/array.rb +36 -0
- data/lib/active_record/connection_adapters/clickhouse/oid/date_time.rb +1 -2
- data/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +15 -2
- data/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +32 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +18 -8
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +145 -81
- data/lib/arel/nodes/settings.rb +11 -0
- data/lib/arel/nodes/using.rb +6 -0
- data/lib/arel/visitors/clickhouse.rb +60 -0
- data/lib/clickhouse-activerecord/migration.rb +37 -15
- data/lib/clickhouse-activerecord/railtie.rb +6 -0
- data/lib/clickhouse-activerecord/tasks.rb +3 -4
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/clickhouse-activerecord.rb +12 -0
- data/lib/core_extensions/active_record/relation.rb +44 -0
- data/lib/core_extensions/arel/nodes/select_statement.rb +18 -0
- data/lib/core_extensions/arel/select_manager.rb +17 -0
- data/lib/{clickhouse-activerecord → core_extensions}/arel/table.rb +4 -2
- data/lib/tasks/clickhouse.rake +26 -24
- metadata +16 -25
- data/lib/clickhouse-activerecord/arel/visitors/to_sql.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6533de4ab9b2415a4df22e72e7ef551d4fb58c5bdc6713ec355b85dbad1174fd
|
|
4
|
+
data.tar.gz: 1c0a898f494c6dc7511897617ea86c2f12aa42461cf693e28e78124bde3638c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6c5c2eba18dce09211cc6b8e5448aaaf2c40ca3eb5cc411aa76874ad6efc00ae257880e5997256808d02771aec7947559edfca941187c219b6724f09b9488f0
|
|
7
|
+
data.tar.gz: 7b7e5f520c4189b4b5a1587789d0ea2ef8b74e86c077611ac18515c45105306c6e17cfd824623ab574420beeb45d205a9f301e2db9f3befd3c3b92c4d303b978
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
### Version 0.5.10 (Jun 22, 2022)
|
|
2
|
+
|
|
3
|
+
* Fixes to create_table method (#70)
|
|
4
|
+
* Added support for rails 7 (#65)
|
|
5
|
+
* Use ClickHouse default KeepAlive timeout of 10 seconds (#67)
|
|
6
|
+
|
|
1
7
|
### Version 0.5.6 (Oct 25, 2021)
|
|
2
|
-
|
|
8
|
+
|
|
3
9
|
* Added auto creating service distributed tables and additional options for creating view [@ygreeek](https://github.com/ygreeek)
|
|
4
10
|
* Added default user agent
|
|
5
11
|
|
|
6
12
|
### Version 0.5.3 (Sep 22, 2021)
|
|
7
|
-
|
|
13
|
+
|
|
8
14
|
* Fix replica cluster for a new syntax MergeTree
|
|
9
15
|
* Fix support rails 5.2 on alter table
|
|
10
16
|
* Support array type of column
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Clickhouse::Activerecord
|
|
2
2
|
|
|
3
3
|
A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 5.2.
|
|
4
|
-
Support ClickHouse version from
|
|
4
|
+
Support ClickHouse version from 22.0 LTS.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
@@ -165,7 +165,7 @@ Structure load from `db/clickhouse_structure.sql` file:
|
|
|
165
165
|
|
|
166
166
|
```ruby
|
|
167
167
|
Action.where(url: 'http://example.com', date: Date.current).where.not(name: nil).order(created_at: :desc).limit(10)
|
|
168
|
-
# Clickhouse Action Load (10.3ms) SELECT
|
|
168
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions WHERE actions.date = '2017-11-29' AND actions.url = 'http://example.com' AND (actions.name IS NOT NULL) ORDER BY actions.created_at DESC LIMIT 10
|
|
169
169
|
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
|
170
170
|
|
|
171
171
|
Action.create(url: 'http://example.com', date: Date.yesterday)
|
|
@@ -175,6 +175,18 @@ Action.create(url: 'http://example.com', date: Date.yesterday)
|
|
|
175
175
|
ActionView.maximum(:date)
|
|
176
176
|
# Clickhouse (10.3ms) SELECT maxMerge(actions.date) FROM actions
|
|
177
177
|
#=> 'Wed, 29 Nov 2017'
|
|
178
|
+
|
|
179
|
+
Action.where(date: Date.current).final.limit(10)
|
|
180
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions FINAL WHERE actions.date = '2017-11-29' LIMIT 10
|
|
181
|
+
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
|
182
|
+
|
|
183
|
+
Action.settings(optimize_read_in_order: 1).where(date: Date.current).limit(10)
|
|
184
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions FINAL WHERE actions.date = '2017-11-29' LIMIT 10 SETTINGS optimize_read_in_order = 1
|
|
185
|
+
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
|
186
|
+
|
|
187
|
+
User.joins(:actions).using(:group_id)
|
|
188
|
+
# Clickhouse User Load (10.3ms) SELECT users.* FROM users INNER JOIN actions USING group_id
|
|
189
|
+
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
|
178
190
|
```
|
|
179
191
|
|
|
180
192
|
|
|
@@ -24,9 +24,8 @@ 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', '
|
|
27
|
+
spec.add_runtime_dependency 'activerecord', '~> 7.0.0'
|
|
28
28
|
|
|
29
|
-
spec.add_development_dependency 'bundler', '~> 1.15'
|
|
30
29
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
31
30
|
spec.add_development_dependency 'rspec', '~> 3.4'
|
|
32
31
|
spec.add_development_dependency 'pry', '~> 0.12'
|
|
@@ -2,21 +2,12 @@ module CoreExtensions
|
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module Migration
|
|
4
4
|
module CommandRecorder
|
|
5
|
-
def create_table_with_distributed(*args, &block)
|
|
6
|
-
record(:create_table_with_distributed, args, &block)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
5
|
def create_view(*args, &block)
|
|
10
6
|
record(:create_view, args, &block)
|
|
11
7
|
end
|
|
12
8
|
|
|
13
9
|
private
|
|
14
10
|
|
|
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
11
|
def invert_create_view(args)
|
|
21
12
|
view_name, options = args
|
|
22
13
|
[:drop_table, view_name, options]
|
|
@@ -23,6 +23,42 @@ module ActiveRecord
|
|
|
23
23
|
@subtype
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def deserialize(value)
|
|
27
|
+
if value.is_a?(::Array)
|
|
28
|
+
value.map { |item| deserialize(item) }
|
|
29
|
+
else
|
|
30
|
+
return value if value.nil?
|
|
31
|
+
case @subtype
|
|
32
|
+
when :integer
|
|
33
|
+
value.to_i
|
|
34
|
+
when :datetime
|
|
35
|
+
::DateTime.parse(value)
|
|
36
|
+
when :date
|
|
37
|
+
::Date.parse(value)
|
|
38
|
+
else
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def serialize(value)
|
|
45
|
+
if value.is_a?(::Array)
|
|
46
|
+
value.map { |item| serialize(item) }
|
|
47
|
+
else
|
|
48
|
+
return value if value.nil?
|
|
49
|
+
case @subtype
|
|
50
|
+
when :integer
|
|
51
|
+
value.to_i
|
|
52
|
+
when :datetime
|
|
53
|
+
DateTime.new.serialize(value)
|
|
54
|
+
when :date
|
|
55
|
+
Date.new.serialize(value)
|
|
56
|
+
else
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
26
62
|
end
|
|
27
63
|
end
|
|
28
64
|
end
|
|
@@ -9,9 +9,8 @@ module ActiveRecord
|
|
|
9
9
|
def serialize(value)
|
|
10
10
|
value = super
|
|
11
11
|
return unless value
|
|
12
|
-
return value.strftime('%Y-%m-%d %H:%M:%S') unless value.acts_like?(:time)
|
|
13
12
|
|
|
14
|
-
value.
|
|
13
|
+
value.strftime('%Y-%m-%d %H:%M:%S' + (@precision.present? && @precision > 0 ? ".%#{@precision}N" : ''))
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def type_cast_from_database(value)
|
|
@@ -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
|
|
@@ -73,7 +82,7 @@ module ActiveRecord
|
|
|
73
82
|
return unless match
|
|
74
83
|
return if match[:database]
|
|
75
84
|
|
|
76
|
-
create_sql << "TO #{current_database}.#{
|
|
85
|
+
create_sql << "TO #{current_database}.#{match[:table_name].sub('.', '')}"
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
def visit_TableDefinition(o)
|
|
@@ -116,7 +125,11 @@ module ActiveRecord
|
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
def current_database
|
|
119
|
-
ActiveRecord::
|
|
128
|
+
if ActiveRecord::version >= Gem::Version.new('6.1')
|
|
129
|
+
ActiveRecord::Base.connection_db_config.database
|
|
130
|
+
else
|
|
131
|
+
ActiveRecord::Base.connection_config[:database]
|
|
132
|
+
end
|
|
120
133
|
end
|
|
121
134
|
end
|
|
122
135
|
end
|
|
@@ -62,6 +62,38 @@ module ActiveRecord
|
|
|
62
62
|
end
|
|
63
63
|
args.each { |name| column(name, kind, **options.except(:limit, :unsigned)) }
|
|
64
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)) }
|
|
96
|
+
end
|
|
65
97
|
end
|
|
66
98
|
end
|
|
67
99
|
end
|
|
@@ -6,8 +6,8 @@ module ActiveRecord
|
|
|
6
6
|
module ConnectionAdapters
|
|
7
7
|
module Clickhouse
|
|
8
8
|
module SchemaStatements
|
|
9
|
-
def execute(sql, name = nil)
|
|
10
|
-
do_execute(sql, name)
|
|
9
|
+
def execute(sql, name = nil, settings: {})
|
|
10
|
+
do_execute(sql, name, settings: settings)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def exec_insert(sql, name, _binds, _pk = nil, _sequence_name = nil)
|
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
|
18
18
|
|
|
19
19
|
def exec_query(sql, name = nil, binds = [], prepare: false)
|
|
20
20
|
result = do_execute(sql, name)
|
|
21
|
-
ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'])
|
|
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
|
|
23
23
|
raise e
|
|
24
24
|
rescue StandardError => e
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def tables(name = nil)
|
|
42
|
-
result = do_system_execute(
|
|
42
|
+
result = do_system_execute("SHOW TABLES WHERE name NOT LIKE '.inner_id.%'", name)
|
|
43
43
|
return [] if result.nil?
|
|
44
44
|
result['data'].flatten
|
|
45
45
|
end
|
|
@@ -92,7 +92,7 @@ module ActiveRecord
|
|
|
92
92
|
if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
|
|
93
93
|
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
|
|
94
94
|
end
|
|
95
|
-
|
|
95
|
+
do_execute(insert_versions_sql(inserting), nil, settings: {max_partitions_per_insert_block: [100, inserting.size].max})
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -105,10 +105,20 @@ module ActiveRecord
|
|
|
105
105
|
def process_response(res)
|
|
106
106
|
case res.code.to_i
|
|
107
107
|
when 200
|
|
108
|
-
res.body.
|
|
108
|
+
if res.body.to_s.include?("DB::Exception")
|
|
109
|
+
raise ActiveRecord::ActiveRecordError, "Response code: #{res.code}:\n#{res.body}"
|
|
110
|
+
else
|
|
111
|
+
res.body.presence && JSON.parse(res.body)
|
|
112
|
+
end
|
|
109
113
|
else
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
case res.body
|
|
115
|
+
when /DB::Exception:.*\(UNKNOWN_DATABASE\)/
|
|
116
|
+
raise ActiveRecord::NoDatabaseError
|
|
117
|
+
when /DB::Exception:.*\(DATABASE_ALREADY_EXISTS\)/
|
|
118
|
+
raise ActiveRecord::DatabaseAlreadyExists
|
|
119
|
+
else
|
|
120
|
+
raise ActiveRecord::ActiveRecordError, "Response code: #{res.code}:\n#{res.body}"
|
|
121
|
+
end
|
|
112
122
|
end
|
|
113
123
|
rescue JSON::ParserError
|
|
114
124
|
res.body
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
3
|
+
require 'arel/visitors/clickhouse'
|
|
4
|
+
require 'arel/nodes/settings'
|
|
5
|
+
require 'arel/nodes/using'
|
|
5
6
|
require 'clickhouse-activerecord/migration'
|
|
6
7
|
require 'active_record/connection_adapters/clickhouse/oid/array'
|
|
7
8
|
require 'active_record/connection_adapters/clickhouse/oid/date'
|
|
@@ -11,6 +12,7 @@ require 'active_record/connection_adapters/clickhouse/schema_definitions'
|
|
|
11
12
|
require 'active_record/connection_adapters/clickhouse/schema_creation'
|
|
12
13
|
require 'active_record/connection_adapters/clickhouse/schema_statements'
|
|
13
14
|
require 'net/http'
|
|
15
|
+
require 'openssl'
|
|
14
16
|
|
|
15
17
|
module ActiveRecord
|
|
16
18
|
class Base
|
|
@@ -32,6 +34,7 @@ module ActiveRecord
|
|
|
32
34
|
sslca: config[:sslca],
|
|
33
35
|
read_timeout: config[:read_timeout],
|
|
34
36
|
write_timeout: config[:write_timeout],
|
|
37
|
+
keep_alive_timeout: config[:keep_alive_timeout]
|
|
35
38
|
}
|
|
36
39
|
end
|
|
37
40
|
|
|
@@ -46,21 +49,6 @@ module ActiveRecord
|
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
|
|
49
|
-
class Relation
|
|
50
|
-
|
|
51
|
-
# Replace for only ClickhouseAdapter
|
|
52
|
-
def reverse_order!
|
|
53
|
-
orders = order_values.uniq
|
|
54
|
-
orders.reject!(&:blank?)
|
|
55
|
-
if self.connection.is_a?(ConnectionAdapters::ClickhouseAdapter) && orders.empty? && !primary_key
|
|
56
|
-
self.order_values = %w(date created_at).select {|c| column_names.include?(c) }.map{|c| arel_attribute(c).desc }
|
|
57
|
-
else
|
|
58
|
-
self.order_values = reverse_sql_order(orders)
|
|
59
|
-
end
|
|
60
|
-
self
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
52
|
module TypeCaster
|
|
65
53
|
class Map
|
|
66
54
|
def is_view
|
|
@@ -74,7 +62,9 @@ module ActiveRecord
|
|
|
74
62
|
end
|
|
75
63
|
|
|
76
64
|
module ModelSchema
|
|
77
|
-
|
|
65
|
+
module ClassMethods
|
|
66
|
+
delegate :final, :settings, to: :all
|
|
67
|
+
|
|
78
68
|
def is_view
|
|
79
69
|
@is_view || false
|
|
80
70
|
end
|
|
@@ -82,13 +72,12 @@ module ActiveRecord
|
|
|
82
72
|
def is_view=(value)
|
|
83
73
|
@is_view = value
|
|
84
74
|
end
|
|
85
|
-
|
|
86
|
-
def arel_table # :nodoc:
|
|
87
|
-
|
|
88
|
-
end
|
|
89
|
-
|
|
75
|
+
#
|
|
76
|
+
# def arel_table # :nodoc:
|
|
77
|
+
# @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
|
|
78
|
+
# end
|
|
90
79
|
end
|
|
91
|
-
|
|
80
|
+
end
|
|
92
81
|
|
|
93
82
|
module ConnectionAdapters
|
|
94
83
|
class ClickhouseColumn < Column
|
|
@@ -104,8 +93,13 @@ module ActiveRecord
|
|
|
104
93
|
float: { name: 'Float32' },
|
|
105
94
|
decimal: { name: 'Decimal' },
|
|
106
95
|
datetime: { name: 'DateTime' },
|
|
96
|
+
datetime64: { name: 'DateTime64' },
|
|
107
97
|
date: { name: 'Date' },
|
|
108
|
-
boolean: { name: '
|
|
98
|
+
boolean: { name: 'Bool' },
|
|
99
|
+
uuid: { name: 'UUID' },
|
|
100
|
+
|
|
101
|
+
enum8: { name: 'Enum8' },
|
|
102
|
+
enum16: { name: 'Enum16' },
|
|
109
103
|
|
|
110
104
|
int8: { name: 'Int8' },
|
|
111
105
|
int16: { name: 'Int16' },
|
|
@@ -154,7 +148,7 @@ module ActiveRecord
|
|
|
154
148
|
end
|
|
155
149
|
|
|
156
150
|
def arel_visitor # :nodoc:
|
|
157
|
-
|
|
151
|
+
Arel::Visitors::Clickhouse.new(self)
|
|
158
152
|
end
|
|
159
153
|
|
|
160
154
|
def native_database_types #:nodoc:
|
|
@@ -165,59 +159,97 @@ module ActiveRecord
|
|
|
165
159
|
!native_database_types[type].nil?
|
|
166
160
|
end
|
|
167
161
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
162
|
+
class << self
|
|
163
|
+
def extract_limit(sql_type) # :nodoc:
|
|
164
|
+
case sql_type
|
|
165
|
+
when /(Nullable)?\(?String\)?/
|
|
166
|
+
super('String')
|
|
167
|
+
when /(Nullable)?\(?U?Int8\)?/
|
|
168
|
+
1
|
|
169
|
+
when /(Nullable)?\(?U?Int16\)?/
|
|
170
|
+
2
|
|
171
|
+
when /(Nullable)?\(?U?Int32\)?/
|
|
172
|
+
nil
|
|
173
|
+
when /(Nullable)?\(?U?Int64\)?/
|
|
174
|
+
8
|
|
175
|
+
else
|
|
176
|
+
super
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
|
|
181
|
+
# except this permits a space after the comma
|
|
182
|
+
|
|
183
|
+
def extract_scale(sql_type)
|
|
184
|
+
case sql_type
|
|
185
|
+
when /\((\d+)\)/ then 0
|
|
186
|
+
when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
|
|
187
|
+
end
|
|
182
188
|
end
|
|
189
|
+
|
|
190
|
+
def extract_precision(sql_type)
|
|
191
|
+
$1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def initialize_type_map(m) # :nodoc:
|
|
195
|
+
super
|
|
196
|
+
register_class_with_limit m, %r(String), Type::String
|
|
197
|
+
register_class_with_limit m, 'Date', Clickhouse::OID::Date
|
|
198
|
+
register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
|
|
199
|
+
|
|
200
|
+
register_class_with_limit m, %r(Int8), Type::Integer
|
|
201
|
+
register_class_with_limit m, %r(Int16), Type::Integer
|
|
202
|
+
register_class_with_limit m, %r(Int32), Type::Integer
|
|
203
|
+
register_class_with_limit m, %r(Int64), Type::Integer
|
|
204
|
+
register_class_with_limit m, %r(Int128), Type::Integer
|
|
205
|
+
register_class_with_limit m, %r(Int256), Type::Integer
|
|
206
|
+
|
|
207
|
+
register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
|
|
208
|
+
register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
|
|
209
|
+
register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
|
|
210
|
+
register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
|
|
211
|
+
#register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
|
|
212
|
+
register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
|
|
213
|
+
# register_class_with_limit m, %r(Array), Clickhouse::OID::Array
|
|
214
|
+
m.register_type(%r(Array)) do |sql_type|
|
|
215
|
+
Clickhouse::OID::Array.new(sql_type)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# In Rails 7 used constant TYPE_MAP, we need redefine method
|
|
221
|
+
def type_map
|
|
222
|
+
@type_map ||= Type::TypeMap.new.tap { |m| ClickhouseAdapter.initialize_type_map(m) }
|
|
183
223
|
end
|
|
184
224
|
|
|
185
|
-
def
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
register_class_with_limit m, %r(Int8), Type::Integer
|
|
192
|
-
register_class_with_limit m, %r(Int16), Type::Integer
|
|
193
|
-
register_class_with_limit m, %r(Int32), Type::Integer
|
|
194
|
-
register_class_with_limit m, %r(Int64), Type::Integer
|
|
195
|
-
register_class_with_limit m, %r(Int128), Type::Integer
|
|
196
|
-
register_class_with_limit m, %r(Int256), Type::Integer
|
|
197
|
-
|
|
198
|
-
register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
|
|
199
|
-
register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
|
|
200
|
-
register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
|
|
201
|
-
register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
|
|
202
|
-
#register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
|
|
203
|
-
register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
|
|
204
|
-
# register_class_with_limit m, %r(Array), Clickhouse::OID::Array
|
|
205
|
-
m.register_type(%r(Array)) do |sql_type|
|
|
206
|
-
Clickhouse::OID::Array.new(sql_type)
|
|
225
|
+
def quote(value)
|
|
226
|
+
case value
|
|
227
|
+
when Array
|
|
228
|
+
'[' + value.map { |v| quote(v) }.join(', ') + ']'
|
|
229
|
+
else
|
|
230
|
+
super
|
|
207
231
|
end
|
|
208
232
|
end
|
|
209
233
|
|
|
210
234
|
# Quoting time without microseconds
|
|
211
235
|
def quoted_date(value)
|
|
212
236
|
if value.acts_like?(:time)
|
|
213
|
-
|
|
237
|
+
if ActiveRecord::version >= Gem::Version.new('7')
|
|
238
|
+
zone_conversion_method = ActiveRecord.default_timezone == :utc ? :getutc : :getlocal
|
|
239
|
+
else
|
|
240
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
|
241
|
+
end
|
|
214
242
|
|
|
215
243
|
if value.respond_to?(zone_conversion_method)
|
|
216
244
|
value = value.send(zone_conversion_method)
|
|
217
245
|
end
|
|
218
246
|
end
|
|
219
247
|
|
|
220
|
-
|
|
248
|
+
if ActiveRecord::version >= Gem::Version.new('7')
|
|
249
|
+
value.to_fs(:db)
|
|
250
|
+
else
|
|
251
|
+
value.to_s(:db)
|
|
252
|
+
end
|
|
221
253
|
end
|
|
222
254
|
|
|
223
255
|
def column_name_for_operation(operation, node) # :nodoc:
|
|
@@ -269,32 +301,30 @@ module ActiveRecord
|
|
|
269
301
|
drop_table(table_name, options.merge(if_exists: true))
|
|
270
302
|
end
|
|
271
303
|
|
|
272
|
-
|
|
304
|
+
do_execute(schema_creation.accept(td), format: nil)
|
|
273
305
|
end
|
|
274
306
|
|
|
275
307
|
def create_table(table_name, **options, &block)
|
|
276
308
|
options = apply_replica(table_name, options)
|
|
277
309
|
td = create_table_definition(apply_cluster(table_name), **options)
|
|
278
310
|
block.call td if block_given?
|
|
311
|
+
td.column(:id, options[:id], null: false) if options[:id].present? && td[:id].blank?
|
|
279
312
|
|
|
280
313
|
if options[:force]
|
|
281
314
|
drop_table(table_name, options.merge(if_exists: true))
|
|
282
315
|
end
|
|
283
316
|
|
|
284
|
-
|
|
285
|
-
end
|
|
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
|
|
317
|
+
do_execute(schema_creation.accept(td), format: nil)
|
|
291
318
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
319
|
+
if options[:with_distributed]
|
|
320
|
+
distributed_table_name = options.delete(:with_distributed)
|
|
321
|
+
sharding_key = options.delete(:sharding_key) || 'rand()'
|
|
322
|
+
raise 'Set a cluster' unless cluster
|
|
295
323
|
|
|
296
|
-
|
|
297
|
-
|
|
324
|
+
distributed_options =
|
|
325
|
+
"Distributed(#{cluster}, #{@config[:database]}, #{table_name}, #{sharding_key})"
|
|
326
|
+
create_table(distributed_table_name, **options.merge(options: distributed_options), &block)
|
|
327
|
+
end
|
|
298
328
|
end
|
|
299
329
|
|
|
300
330
|
# Drops a ClickHouse database.
|
|
@@ -311,11 +341,36 @@ module ActiveRecord
|
|
|
311
341
|
end
|
|
312
342
|
|
|
313
343
|
def drop_table(table_name, options = {}) # :nodoc:
|
|
314
|
-
|
|
344
|
+
query = "DROP TABLE"
|
|
345
|
+
query = "#{query} IF EXISTS " if options[:if_exists]
|
|
346
|
+
query = "#{query} #{quote_table_name(table_name)}"
|
|
347
|
+
query = apply_cluster(query)
|
|
348
|
+
query = "#{query} SYNC" if options[:sync]
|
|
349
|
+
|
|
350
|
+
do_execute(query)
|
|
351
|
+
|
|
352
|
+
if options[:with_distributed]
|
|
353
|
+
distributed_table_name = options.delete(:with_distributed)
|
|
354
|
+
drop_table(distributed_table_name, **options)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def add_column(table_name, column_name, type, **options)
|
|
359
|
+
return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
|
|
360
|
+
|
|
361
|
+
at = create_alter_table table_name
|
|
362
|
+
at.add_column(column_name, type, **options)
|
|
363
|
+
execute(schema_creation.accept(at), nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
|
367
|
+
return if options[:if_exists] == true && !column_exists?(table_name, column_name)
|
|
368
|
+
|
|
369
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}", nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
315
370
|
end
|
|
316
371
|
|
|
317
372
|
def change_column(table_name, column_name, type, options = {})
|
|
318
|
-
result = do_execute
|
|
373
|
+
result = do_execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}", nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
319
374
|
raise "Error parse json response: #{result}" if result.presence && !result.is_a?(Hash)
|
|
320
375
|
end
|
|
321
376
|
|
|
@@ -351,12 +406,18 @@ module ActiveRecord
|
|
|
351
406
|
|
|
352
407
|
def database_engine_atomic?
|
|
353
408
|
current_database_engine = "select engine from system.databases where name = '#{@config[:database]}'"
|
|
354
|
-
res =
|
|
409
|
+
res = select_one(current_database_engine)
|
|
355
410
|
res['engine'] == 'Atomic' if res
|
|
356
411
|
end
|
|
357
412
|
|
|
358
413
|
def apply_cluster(sql)
|
|
359
|
-
|
|
414
|
+
if cluster
|
|
415
|
+
normalized_cluster_name = cluster.start_with?('{') ? "'#{cluster}'" : cluster
|
|
416
|
+
|
|
417
|
+
"#{sql} ON CLUSTER #{normalized_cluster_name}"
|
|
418
|
+
else
|
|
419
|
+
sql
|
|
420
|
+
end
|
|
360
421
|
end
|
|
361
422
|
|
|
362
423
|
def supports_insert_on_duplicate_skip?
|
|
@@ -393,6 +454,9 @@ module ActiveRecord
|
|
|
393
454
|
@connection.read_timeout = @connection_parameters[:read_timeout] if @connection_parameters[:read_timeout]
|
|
394
455
|
@connection.write_timeout = @connection_parameters[:write_timeout] if @connection_parameters[:write_timeout]
|
|
395
456
|
|
|
457
|
+
# Use clickhouse default keep_alive_timeout value of 10, rather than Net::HTTP's default of 2
|
|
458
|
+
@connection.keep_alive_timeout = @connection_parameters[:keep_alive_timeout] || 10
|
|
459
|
+
|
|
396
460
|
@connection
|
|
397
461
|
end
|
|
398
462
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'arel/visitors/to_sql'
|
|
2
|
+
|
|
3
|
+
module Arel
|
|
4
|
+
module Visitors
|
|
5
|
+
class Clickhouse < ::Arel::Visitors::ToSql
|
|
6
|
+
|
|
7
|
+
def aggregate(name, o, collector)
|
|
8
|
+
# replacing function name for materialized view
|
|
9
|
+
if o.expressions.first && o.expressions.first != '*' && !o.expressions.first.is_a?(String) && o.expressions.first.relation&.is_view
|
|
10
|
+
super("#{name.downcase}Merge", o, collector)
|
|
11
|
+
else
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def visit_Arel_Table o, collector
|
|
17
|
+
collector = super
|
|
18
|
+
collector << ' FINAL ' if o.final
|
|
19
|
+
collector
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visit_Arel_Nodes_SelectOptions(o, collector)
|
|
23
|
+
maybe_visit o.settings, super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visit_Arel_Nodes_Settings(o, collector)
|
|
27
|
+
return collector if o.expr.empty?
|
|
28
|
+
|
|
29
|
+
collector << "SETTINGS "
|
|
30
|
+
o.expr.each_with_index do |(key, value), i|
|
|
31
|
+
collector << ", " if i > 0
|
|
32
|
+
collector << key.to_s.gsub(/\W+/, "")
|
|
33
|
+
collector << " = "
|
|
34
|
+
collector << sanitize_as_setting_value(value)
|
|
35
|
+
end
|
|
36
|
+
collector
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def visit_Arel_Nodes_Using o, collector
|
|
40
|
+
collector << "USING "
|
|
41
|
+
visit o.expr, collector
|
|
42
|
+
collector
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sanitize_as_setting_value(value)
|
|
46
|
+
if value == :default
|
|
47
|
+
'DEFAULT'
|
|
48
|
+
else
|
|
49
|
+
quote(value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def sanitize_as_setting_name(value)
|
|
54
|
+
return value if Arel::Nodes::SqlLiteral === value
|
|
55
|
+
@connection.sanitize_as_setting_name(value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -10,16 +10,17 @@ module ClickhouseActiverecord
|
|
|
10
10
|
|
|
11
11
|
version_options = connection.internal_string_options_for_primary_key
|
|
12
12
|
table_options = {
|
|
13
|
-
id: false, options: 'ReplacingMergeTree(ver)
|
|
13
|
+
id: false, options: 'ReplacingMergeTree(ver) ORDER BY (version)', if_not_exists: true
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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'}"
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
connection.
|
|
23
|
+
connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
|
|
23
24
|
t.string :version, **version_options
|
|
24
25
|
t.column :active, 'Int8', null: false, default: '1'
|
|
25
26
|
t.datetime :ver, null: false, default: -> { 'now()' }
|
|
@@ -27,30 +28,43 @@ module ClickhouseActiverecord
|
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def all_versions
|
|
30
|
-
|
|
31
|
+
final.where(active: 1).order(:version).pluck(:version)
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
|
36
37
|
class << self
|
|
38
|
+
|
|
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
|
|
45
|
+
|
|
46
|
+
def [](key)
|
|
47
|
+
final.where(key: key).pluck(:value).first
|
|
48
|
+
end
|
|
49
|
+
|
|
37
50
|
def create_table
|
|
38
51
|
return if table_exists?
|
|
39
52
|
|
|
40
53
|
key_options = connection.internal_string_options_for_primary_key
|
|
41
54
|
table_options = {
|
|
42
55
|
id: false,
|
|
43
|
-
options: connection.adapter_name.downcase == 'clickhouse' ? '
|
|
56
|
+
options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
|
|
44
57
|
if_not_exists: true
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
full_config = connection.instance_variable_get(:@full_config) || {}
|
|
60
|
+
|
|
61
|
+
if full_config[:distributed_service_tables]
|
|
62
|
+
table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
|
|
63
|
+
|
|
64
|
+
distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
|
|
51
65
|
end
|
|
52
66
|
|
|
53
|
-
connection.
|
|
67
|
+
connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
|
|
54
68
|
t.string :key, **key_options
|
|
55
69
|
t.string :value
|
|
56
70
|
t.timestamps
|
|
@@ -120,5 +134,13 @@ module ClickhouseActiverecord
|
|
|
120
134
|
super
|
|
121
135
|
end
|
|
122
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
|
+
|
|
123
145
|
end
|
|
124
146
|
end
|
|
@@ -4,6 +4,12 @@ module ClickhouseActiverecord
|
|
|
4
4
|
require 'rails'
|
|
5
5
|
|
|
6
6
|
class Railtie < Rails::Railtie
|
|
7
|
+
initializer "clickhouse.load" do
|
|
8
|
+
ActiveSupport.on_load :active_record do
|
|
9
|
+
ClickhouseActiverecord.load
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
rake_tasks { load 'tasks/clickhouse.rake' }
|
|
8
14
|
end
|
|
9
15
|
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,10 +10,10 @@ module ClickhouseActiverecord
|
|
|
11
10
|
|
|
12
11
|
def create
|
|
13
12
|
establish_master_connection
|
|
14
|
-
connection.create_database @configuration[
|
|
13
|
+
connection.create_database @configuration['database']
|
|
15
14
|
rescue ActiveRecord::StatementInvalid => e
|
|
16
15
|
if e.cause.to_s.include?('already exists')
|
|
17
|
-
raise ActiveRecord::
|
|
16
|
+
raise ActiveRecord::DatabaseAlreadyExists
|
|
18
17
|
else
|
|
19
18
|
raise
|
|
20
19
|
end
|
|
@@ -22,7 +21,7 @@ module ClickhouseActiverecord
|
|
|
22
21
|
|
|
23
22
|
def drop
|
|
24
23
|
establish_master_connection
|
|
25
|
-
connection.drop_database @configuration[
|
|
24
|
+
connection.drop_database @configuration['database']
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def purge
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'active_record/connection_adapters/clickhouse_adapter'
|
|
4
4
|
|
|
5
|
+
require 'core_extensions/active_record/relation'
|
|
6
|
+
|
|
7
|
+
require 'core_extensions/arel/nodes/select_statement'
|
|
8
|
+
require 'core_extensions/arel/select_manager'
|
|
9
|
+
require 'core_extensions/arel/table'
|
|
10
|
+
|
|
5
11
|
require_relative '../core_extensions/active_record/migration/command_recorder'
|
|
6
12
|
ActiveRecord::Migration::CommandRecorder.include CoreExtensions::ActiveRecord::Migration::CommandRecorder
|
|
7
13
|
|
|
@@ -14,5 +20,11 @@ if defined?(Rails::Railtie)
|
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
module ClickhouseActiverecord
|
|
23
|
+
def self.load
|
|
24
|
+
ActiveRecord::Relation.prepend(CoreExtensions::ActiveRecord::Relation)
|
|
17
25
|
|
|
26
|
+
Arel::Nodes::SelectStatement.prepend(CoreExtensions::Arel::Nodes::SelectStatement)
|
|
27
|
+
Arel::SelectManager.prepend(CoreExtensions::Arel::SelectManager)
|
|
28
|
+
Arel::Table.prepend(CoreExtensions::Arel::Table)
|
|
29
|
+
end
|
|
18
30
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module CoreExtensions
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Relation
|
|
4
|
+
def reverse_order!
|
|
5
|
+
return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
|
|
6
|
+
|
|
7
|
+
orders = order_values.uniq.reject(&:blank?)
|
|
8
|
+
return super unless orders.empty? && !primary_key
|
|
9
|
+
|
|
10
|
+
self.order_values = (column_names & %w[date created_at]).map { |c| arel_table[c].desc }
|
|
11
|
+
self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param [Hash] opts
|
|
15
|
+
def settings(**opts)
|
|
16
|
+
check_command('SETTINGS')
|
|
17
|
+
@values[:settings] = (@values[:settings] || {}).merge opts
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [Boolean] final
|
|
22
|
+
def final(final = true)
|
|
23
|
+
check_command('FINAL')
|
|
24
|
+
@table = @table.dup
|
|
25
|
+
@table.final = final
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def check_command(cmd)
|
|
32
|
+
raise ::ActiveRecord::ActiveRecordError, cmd + ' is a ClickHouse specific query clause' unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_arel(aliases = nil)
|
|
36
|
+
arel = super
|
|
37
|
+
|
|
38
|
+
arel.settings(@values[:settings]) if @values[:settings].present?
|
|
39
|
+
|
|
40
|
+
arel
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module CoreExtensions
|
|
2
|
+
module Arel # :nodoc: all
|
|
3
|
+
module Nodes
|
|
4
|
+
module SelectStatement
|
|
5
|
+
attr_accessor :settings
|
|
6
|
+
|
|
7
|
+
def initialize(relation = nil)
|
|
8
|
+
super
|
|
9
|
+
@settings = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def eql?(other)
|
|
13
|
+
super && settings == other.settings
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module CoreExtensions
|
|
2
|
+
module Arel
|
|
3
|
+
module SelectManager
|
|
4
|
+
|
|
5
|
+
# @param [Hash] values
|
|
6
|
+
def settings(values)
|
|
7
|
+
@ast.settings = ::Arel::Nodes::Settings.new(values)
|
|
8
|
+
self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def using(*exprs)
|
|
12
|
+
@ctx.source.right.last.right = ::Arel::Nodes::Using.new(::Arel.sql(exprs.join(',')))
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/tasks/clickhouse.rake
CHANGED
|
@@ -1,68 +1,70 @@
|
|
|
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.
|
|
5
|
+
ClickhouseActiverecord::SchemaMigration.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
task prepare_internal_metadata_table: :environment do
|
|
10
|
-
ClickhouseActiverecord::InternalMetadata.create_table unless ENV['simple'] || ARGV.
|
|
9
|
+
ClickhouseActiverecord::InternalMetadata.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
task load_config: :environment do
|
|
14
|
-
ENV['SCHEMA'] =
|
|
15
|
-
ActiveRecord::Migrator.migrations_paths = [
|
|
16
|
-
ActiveRecord::Base.establish_connection(:
|
|
13
|
+
ENV['SCHEMA'] = 'db/clickhouse_schema.rb'
|
|
14
|
+
ActiveRecord::Migrator.migrations_paths = %w[db/migrate_clickhouse]
|
|
15
|
+
ActiveRecord::Base.establish_connection(:clickhouse)
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
namespace :schema do
|
|
20
|
-
|
|
21
|
-
# todo not testing
|
|
19
|
+
# TODO: not testing
|
|
22
20
|
desc 'Load database schema'
|
|
23
|
-
task load: [
|
|
24
|
-
simple = ENV['simple'] || ARGV.
|
|
21
|
+
task load: %i[load_config prepare_internal_metadata_table] do
|
|
22
|
+
simple = ENV['simple'] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
|
|
25
23
|
ClickhouseActiverecord::SchemaMigration.drop_table
|
|
26
|
-
load(
|
|
24
|
+
load(Rails.root.join("db/clickhouse_schema#{simple}.rb"))
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
desc 'Dump database schema'
|
|
30
|
-
task dump: :environment do |
|
|
31
|
-
simple = ENV['simple'] || args[:simple] || ARGV.
|
|
32
|
-
filename =
|
|
28
|
+
task dump: :environment do |_, args|
|
|
29
|
+
simple = ENV['simple'] || args[:simple] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
|
|
30
|
+
filename = Rails.root.join("db/clickhouse_schema#{simple}.rb")
|
|
33
31
|
File.open(filename, 'w:utf-8') do |file|
|
|
34
|
-
ActiveRecord::Base.establish_connection(:
|
|
35
|
-
ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base,
|
|
32
|
+
ActiveRecord::Base.establish_connection(:clickhouse)
|
|
33
|
+
ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, simple.present?)
|
|
36
34
|
end
|
|
37
35
|
end
|
|
38
|
-
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
namespace :structure do
|
|
42
39
|
desc 'Load database structure'
|
|
43
40
|
task load: [:load_config, 'db:check_protected_environments'] do
|
|
44
|
-
|
|
41
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
|
|
42
|
+
ClickhouseActiverecord::Tasks.new(config).structure_load(Rails.root.join('db/clickhouse_structure.sql'))
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
desc 'Dump database structure'
|
|
48
46
|
task dump: [:load_config, 'db:check_protected_environments'] do
|
|
49
|
-
|
|
47
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
|
|
48
|
+
ClickhouseActiverecord::Tasks.new(config).structure_dump(Rails.root.join('db/clickhouse_structure.sql'))
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
desc 'Creates the database from DATABASE_URL or config/database.yml'
|
|
54
53
|
task create: [:load_config] do
|
|
55
|
-
ActiveRecord::
|
|
54
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
|
|
55
|
+
ActiveRecord::Tasks::DatabaseTasks.create(config)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
desc 'Drops the database from DATABASE_URL or config/database.yml'
|
|
59
59
|
task drop: [:load_config, 'db:check_protected_environments'] do
|
|
60
|
-
ActiveRecord::
|
|
60
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
|
|
61
|
+
ActiveRecord::Tasks::DatabaseTasks.drop(config)
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
desc 'Empty the database from DATABASE_URL or config/database.yml'
|
|
64
65
|
task purge: [:load_config, 'db:check_protected_environments'] do
|
|
65
|
-
ActiveRecord::
|
|
66
|
+
config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
|
|
67
|
+
ActiveRecord::Tasks::DatabaseTasks.purge(config)
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
# desc 'Resets your database using your migrations for the current environment'
|
|
@@ -72,7 +74,7 @@ namespace :clickhouse do
|
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
desc 'Migrate the clickhouse database'
|
|
75
|
-
task migrate: [
|
|
77
|
+
task migrate: %i[load_config prepare_schema_migration_table prepare_internal_metadata_table] do
|
|
76
78
|
Rake::Task['db:migrate'].execute
|
|
77
79
|
if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
|
|
78
80
|
Rake::Task['clickhouse:schema:dump'].execute(simple: true)
|
|
@@ -80,7 +82,7 @@ namespace :clickhouse do
|
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
desc 'Rollback the clickhouse database'
|
|
83
|
-
task rollback: [
|
|
85
|
+
task rollback: %i[load_config prepare_schema_migration_table prepare_internal_metadata_table] do
|
|
84
86
|
Rake::Task['db:rollback'].execute
|
|
85
87
|
end
|
|
86
88
|
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: 0.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergey Odintsov
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-11-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -26,32 +26,18 @@ dependencies:
|
|
|
26
26
|
version: 1.13.4
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: activerecord
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '5.2'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '5.2'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: bundler
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
44
30
|
requirements:
|
|
45
31
|
- - "~>"
|
|
46
32
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
48
|
-
type: :
|
|
33
|
+
version: 7.0.0
|
|
34
|
+
type: :runtime
|
|
49
35
|
prerelease: false
|
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
37
|
requirements:
|
|
52
38
|
- - "~>"
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
40
|
+
version: 7.0.0
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
42
|
name: rake
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -121,22 +107,27 @@ files:
|
|
|
121
107
|
- lib/active_record/connection_adapters/clickhouse/schema_definitions.rb
|
|
122
108
|
- lib/active_record/connection_adapters/clickhouse/schema_statements.rb
|
|
123
109
|
- lib/active_record/connection_adapters/clickhouse_adapter.rb
|
|
110
|
+
- lib/arel/nodes/settings.rb
|
|
111
|
+
- lib/arel/nodes/using.rb
|
|
112
|
+
- lib/arel/visitors/clickhouse.rb
|
|
124
113
|
- lib/clickhouse-activerecord.rb
|
|
125
|
-
- lib/clickhouse-activerecord/arel/table.rb
|
|
126
|
-
- lib/clickhouse-activerecord/arel/visitors/to_sql.rb
|
|
127
114
|
- lib/clickhouse-activerecord/migration.rb
|
|
128
115
|
- lib/clickhouse-activerecord/railtie.rb
|
|
129
116
|
- lib/clickhouse-activerecord/schema.rb
|
|
130
117
|
- lib/clickhouse-activerecord/schema_dumper.rb
|
|
131
118
|
- lib/clickhouse-activerecord/tasks.rb
|
|
132
119
|
- lib/clickhouse-activerecord/version.rb
|
|
120
|
+
- lib/core_extensions/active_record/relation.rb
|
|
121
|
+
- lib/core_extensions/arel/nodes/select_statement.rb
|
|
122
|
+
- lib/core_extensions/arel/select_manager.rb
|
|
123
|
+
- lib/core_extensions/arel/table.rb
|
|
133
124
|
- lib/generators/clickhouse_migration_generator.rb
|
|
134
125
|
- lib/tasks/clickhouse.rake
|
|
135
126
|
homepage: https://github.com/pnixx/clickhouse-activerecord
|
|
136
127
|
licenses:
|
|
137
128
|
- MIT
|
|
138
129
|
metadata: {}
|
|
139
|
-
post_install_message:
|
|
130
|
+
post_install_message:
|
|
140
131
|
rdoc_options: []
|
|
141
132
|
require_paths:
|
|
142
133
|
- lib
|
|
@@ -151,8 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
151
142
|
- !ruby/object:Gem::Version
|
|
152
143
|
version: '0'
|
|
153
144
|
requirements: []
|
|
154
|
-
rubygems_version: 3.
|
|
155
|
-
signing_key:
|
|
145
|
+
rubygems_version: 3.1.6
|
|
146
|
+
signing_key:
|
|
156
147
|
specification_version: 4
|
|
157
148
|
summary: ClickHouse ActiveRecord
|
|
158
149
|
test_files: []
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
require 'arel/visitors/to_sql'
|
|
2
|
-
|
|
3
|
-
module ClickhouseActiverecord
|
|
4
|
-
module Arel
|
|
5
|
-
module Visitors
|
|
6
|
-
class ToSql < ::Arel::Visitors::ToSql
|
|
7
|
-
|
|
8
|
-
def aggregate(name, o, collector)
|
|
9
|
-
# replacing function name for materialized view
|
|
10
|
-
if o.expressions.first && o.expressions.first != '*' && !o.expressions.first.is_a?(String) && o.expressions.first.relation&.is_view
|
|
11
|
-
super("#{name.downcase}Merge", o, collector)
|
|
12
|
-
else
|
|
13
|
-
super
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|