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