clickhouse-activerecord 0.3.11 → 0.4.3
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 +7 -0
- data/README.md +27 -4
- data/clickhouse-activerecord.gemspec +1 -1
- data/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +20 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +29 -0
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +27 -7
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +64 -8
- data/lib/clickhouse-activerecord.rb +1 -0
- data/lib/clickhouse-activerecord/migration.rb +92 -0
- data/lib/clickhouse-activerecord/schema.rb +19 -0
- data/lib/clickhouse-activerecord/schema_dumper.rb +120 -3
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/generators/clickhouse_migration_generator.rb +2 -2
- data/lib/tasks/clickhouse.rake +24 -6
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b584e48174f94997dc9dd04d3ffde3ff7c4b24535117c565e580c646f2f2581c
|
4
|
+
data.tar.gz: 9e7c01a185d23ac6e5ac16da635b118a59e7a7c7cdf86ce1c28e92aebacd3baa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b3132522dbc7abaffa180049e350d4ceea82863ab3c208611c4b3227e6ebece1f706262daebec562bd13822fdcd31d44e6321ec97083543484e238e3e07ed2f
|
7
|
+
data.tar.gz: 44f3c58ff30e78b4e83066fbf9609bd656bf9c3e64f56a6113713068b98393c680ec949b5d8832da533ea6cc12216584a9fcf7b34279efc8c499ec9ff376a166
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
### Version 0.4.0 (Sep 18, 2020)
|
2
|
+
|
3
|
+
* Full support migration and rollback database
|
4
|
+
* Support cluster and replica. Auto inject to SQL queries.
|
5
|
+
* Fix schema dump/load
|
6
|
+
* Can dump schema for using PostgreSQL
|
7
|
+
|
1
8
|
### Version 0.3.10 (Dec 20, 2019)
|
2
9
|
|
3
10
|
* Support structure dump/load [@StoneGod](https://github.com/StoneGod)
|
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
|
-
|
4
|
+
Support ClickHouse version from 19.14 LTS.
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
|
@@ -31,6 +31,8 @@ default: &default
|
|
31
31
|
ssl: true # optional for using ssl connection
|
32
32
|
debug: true # use for showing in to log technical information
|
33
33
|
migrations_paths: db/clickhouse # optional, default: db/migrate_clickhouse
|
34
|
+
cluster_name: 'cluster_name' # optional for creating tables in cluster
|
35
|
+
replica_name: '{replica}' # replica macros name, optional for creating replicated tables
|
34
36
|
```
|
35
37
|
|
36
38
|
## Usage in Rails 5
|
@@ -99,13 +101,17 @@ Create / drop / purge / reset database:
|
|
99
101
|
$ rake clickhouse:drop
|
100
102
|
$ rake clickhouse:purge
|
101
103
|
$ rake clickhouse:reset
|
104
|
+
|
105
|
+
Prepare system tables for rails:
|
106
|
+
|
107
|
+
$ rake clickhouse:prepare_schema_migration_table
|
108
|
+
$ rake clickhouse:prepare_internal_metadata_table
|
102
109
|
|
103
110
|
Migration:
|
104
111
|
|
105
112
|
$ rails g clickhouse_migration MIGRATION_NAME COLUMNS
|
106
113
|
$ rake clickhouse:migrate
|
107
|
-
|
108
|
-
Rollback migration not supported!
|
114
|
+
$ rake clickhouse:rollback
|
109
115
|
|
110
116
|
### Dump / Load for multiple using databases
|
111
117
|
|
@@ -119,7 +125,11 @@ Schema load from `db/clickhouse_schema.rb` file:
|
|
119
125
|
|
120
126
|
$ rake clickhouse:schema:load
|
121
127
|
|
122
|
-
|
128
|
+
For export schema to PostgreSQL, you need use:
|
129
|
+
|
130
|
+
$ rake clickhouse:schema:dump -- --simple
|
131
|
+
|
132
|
+
Schema will be dump to `db/clickhouse_schema_simple.rb`. If default file exists, it will be auto update after migration.
|
123
133
|
|
124
134
|
Structure dump to `db/clickhouse_structure.sql` file:
|
125
135
|
|
@@ -152,6 +162,19 @@ ActionView.maximum(:date)
|
|
152
162
|
#=> 'Wed, 29 Nov 2017'
|
153
163
|
```
|
154
164
|
|
165
|
+
### Using replica and cluster params in connection parameters
|
166
|
+
|
167
|
+
```yml
|
168
|
+
default: &default
|
169
|
+
***
|
170
|
+
cluster_name: 'cluster_name'
|
171
|
+
replica_name: '{replica}'
|
172
|
+
```
|
173
|
+
|
174
|
+
`ON CLUSTER cluster_name` will be attach to all queries create / drop.
|
175
|
+
|
176
|
+
Engines `MergeTree` and all support replication engines will be replaced to `Replicated***('/clickhouse/tables/cluster_name/database.table', '{replica}')`
|
177
|
+
|
155
178
|
## Donations
|
156
179
|
|
157
180
|
Donations to this project are going directly to [PNixx](https://github.com/PNixx), the original author of this project:
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_runtime_dependency 'activerecord', '>= 5.2'
|
28
28
|
|
29
29
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
30
|
-
spec.add_development_dependency 'rake', '~>
|
30
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
31
31
|
spec.add_development_dependency 'rspec', '~> 3.4'
|
32
32
|
spec.add_development_dependency 'pry', '~> 0.12'
|
33
33
|
end
|
@@ -27,6 +27,26 @@ module ActiveRecord
|
|
27
27
|
|
28
28
|
create_sql
|
29
29
|
end
|
30
|
+
|
31
|
+
def visit_TableDefinition(o)
|
32
|
+
create_sql = +"CREATE#{table_modifier_in_create(o)} #{o.view ? "VIEW" : "TABLE"} "
|
33
|
+
create_sql << "IF NOT EXISTS " if o.if_not_exists
|
34
|
+
create_sql << "#{quote_table_name(o.name)} "
|
35
|
+
|
36
|
+
statements = o.columns.map { |c| accept c }
|
37
|
+
statements << accept(o.primary_keys) if o.primary_keys
|
38
|
+
|
39
|
+
create_sql << "(#{statements.join(', ')})" if statements.present?
|
40
|
+
add_table_options!(create_sql, table_options(o))
|
41
|
+
create_sql << " AS #{to_sql(o.as)}" if o.as
|
42
|
+
create_sql
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns any SQL string to go between CREATE and TABLE. May be nil.
|
46
|
+
def table_modifier_in_create(o)
|
47
|
+
" TEMPORARY" if o.temporary
|
48
|
+
" MATERIALIZED" if o.materialized
|
49
|
+
end
|
30
50
|
end
|
31
51
|
end
|
32
52
|
end
|
@@ -5,6 +5,35 @@ module ActiveRecord
|
|
5
5
|
module Clickhouse
|
6
6
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
7
7
|
|
8
|
+
attr_reader :view, :materialized, :if_not_exists
|
9
|
+
|
10
|
+
def initialize(
|
11
|
+
conn,
|
12
|
+
name,
|
13
|
+
temporary: false,
|
14
|
+
if_not_exists: false,
|
15
|
+
options: nil,
|
16
|
+
as: nil,
|
17
|
+
comment: nil,
|
18
|
+
view: false,
|
19
|
+
materialized: false,
|
20
|
+
**
|
21
|
+
)
|
22
|
+
@conn = conn
|
23
|
+
@columns_hash = {}
|
24
|
+
@indexes = []
|
25
|
+
@foreign_keys = []
|
26
|
+
@primary_keys = nil
|
27
|
+
@temporary = temporary
|
28
|
+
@if_not_exists = if_not_exists
|
29
|
+
@options = options
|
30
|
+
@as = as
|
31
|
+
@name = @conn.apply_cluster(name)
|
32
|
+
@comment = comment
|
33
|
+
@view = view || materialized
|
34
|
+
@materialized = materialized
|
35
|
+
end
|
36
|
+
|
8
37
|
def integer(*args, **options)
|
9
38
|
if options[:limit] == 8
|
10
39
|
args.each { |name| column(name, :big_integer, options.except(:limit)) }
|
@@ -17,6 +17,8 @@ module ActiveRecord
|
|
17
17
|
def exec_query(sql, name = nil, binds = [], prepare: false)
|
18
18
|
result = do_execute(sql, name)
|
19
19
|
ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'])
|
20
|
+
rescue StandardError => _e
|
21
|
+
raise ActiveRecord::ActiveRecordError, "Response: #{result}"
|
20
22
|
end
|
21
23
|
|
22
24
|
def exec_update(_sql, _name = nil, _binds = [])
|
@@ -34,7 +36,7 @@ module ActiveRecord
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def table_options(table)
|
37
|
-
sql = do_system_execute("SHOW CREATE TABLE
|
39
|
+
sql = do_system_execute("SHOW CREATE TABLE `#{table}`")['data'].try(:first).try(:first)
|
38
40
|
{ options: sql.gsub(/^(?:.*?)ENGINE = (.*?)$/, '\\1') }
|
39
41
|
end
|
40
42
|
|
@@ -65,6 +67,26 @@ module ActiveRecord
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
70
|
+
def assume_migrated_upto_version(version, migrations_paths = nil)
|
71
|
+
version = version.to_i
|
72
|
+
sm_table = quote_table_name(schema_migration.table_name)
|
73
|
+
|
74
|
+
migrated = migration_context.get_all_versions
|
75
|
+
versions = migration_context.migrations.map(&:version)
|
76
|
+
|
77
|
+
unless migrated.include?(version)
|
78
|
+
exec_insert "INSERT INTO #{sm_table} (version) VALUES (#{quote(version.to_s)})", nil, nil
|
79
|
+
end
|
80
|
+
|
81
|
+
inserting = (versions - migrated).select { |v| v < version }
|
82
|
+
if inserting.any?
|
83
|
+
if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
|
84
|
+
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
|
85
|
+
end
|
86
|
+
execute insert_versions_sql(inserting)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
68
90
|
private
|
69
91
|
|
70
92
|
def apply_format(sql, format)
|
@@ -79,6 +101,8 @@ module ActiveRecord
|
|
79
101
|
raise ActiveRecord::ActiveRecordError,
|
80
102
|
"Response code: #{res.code}:\n#{res.body}"
|
81
103
|
end
|
104
|
+
rescue JSON::ParserError
|
105
|
+
res.body
|
82
106
|
end
|
83
107
|
|
84
108
|
def log_with_debug(sql, name = nil)
|
@@ -91,11 +115,7 @@ module ActiveRecord
|
|
91
115
|
end
|
92
116
|
|
93
117
|
def create_table_definition(*args)
|
94
|
-
|
95
|
-
Clickhouse::TableDefinition.new(self, *args)
|
96
|
-
else
|
97
|
-
Clickhouse::TableDefinition.new(*args)
|
98
|
-
end
|
118
|
+
Clickhouse::TableDefinition.new(self, *args)
|
99
119
|
end
|
100
120
|
|
101
121
|
def new_column_from_field(table_name, field)
|
@@ -114,7 +134,7 @@ module ActiveRecord
|
|
114
134
|
protected
|
115
135
|
|
116
136
|
def table_structure(table_name)
|
117
|
-
result = do_system_execute("DESCRIBE TABLE
|
137
|
+
result = do_system_execute("DESCRIBE TABLE `#{table_name}`", table_name)
|
118
138
|
data = result['data']
|
119
139
|
|
120
140
|
return data unless data.empty?
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'clickhouse-activerecord/arel/visitors/to_sql'
|
4
4
|
require 'clickhouse-activerecord/arel/table'
|
5
|
+
require 'clickhouse-activerecord/migration'
|
5
6
|
require 'active_record/connection_adapters/clickhouse/oid/date'
|
6
7
|
require 'active_record/connection_adapters/clickhouse/oid/date_time'
|
7
8
|
require 'active_record/connection_adapters/clickhouse/oid/big_integer'
|
@@ -110,17 +111,17 @@ module ActiveRecord
|
|
110
111
|
|
111
112
|
# Support SchemaMigration from v5.2.2 to v6+
|
112
113
|
def schema_migration # :nodoc:
|
113
|
-
|
114
|
-
super
|
115
|
-
else
|
116
|
-
ActiveRecord::SchemaMigration
|
117
|
-
end
|
114
|
+
ClickhouseActiverecord::SchemaMigration
|
118
115
|
end
|
119
116
|
|
120
117
|
def migrations_paths
|
121
118
|
@full_config[:migrations_paths] || 'db/migrate_clickhouse'
|
122
119
|
end
|
123
120
|
|
121
|
+
def migration_context # :nodoc:
|
122
|
+
ClickhouseActiverecord::MigrationContext.new(migrations_paths, schema_migration)
|
123
|
+
end
|
124
|
+
|
124
125
|
def arel_visitor # :nodoc:
|
125
126
|
ClickhouseActiverecord::Arel::Visitors::ToSql.new(self)
|
126
127
|
end
|
@@ -202,24 +203,69 @@ module ActiveRecord
|
|
202
203
|
|
203
204
|
# Create a new ClickHouse database.
|
204
205
|
def create_database(name)
|
205
|
-
sql = "CREATE DATABASE #{quote_table_name(name)}"
|
206
|
+
sql = apply_cluster "CREATE DATABASE #{quote_table_name(name)}"
|
206
207
|
log_with_debug(sql, adapter_name) do
|
207
208
|
res = @connection.post("/?#{@config.except(:database).to_param}", "CREATE DATABASE #{quote_table_name(name)}")
|
208
209
|
process_response(res)
|
209
210
|
end
|
210
211
|
end
|
211
212
|
|
213
|
+
def create_view(table_name, **options)
|
214
|
+
options.merge!(view: true)
|
215
|
+
options = apply_replica(table_name, options)
|
216
|
+
td = create_table_definition(table_name, options)
|
217
|
+
yield td if block_given?
|
218
|
+
|
219
|
+
if options[:force]
|
220
|
+
drop_table(table_name, options.merge(if_exists: true))
|
221
|
+
end
|
222
|
+
|
223
|
+
execute schema_creation.accept td
|
224
|
+
end
|
225
|
+
|
226
|
+
def create_table(table_name, **options)
|
227
|
+
options = apply_replica(table_name, options)
|
228
|
+
td = create_table_definition(table_name, options)
|
229
|
+
yield td if block_given?
|
230
|
+
|
231
|
+
if options[:force]
|
232
|
+
drop_table(table_name, options.merge(if_exists: true))
|
233
|
+
end
|
234
|
+
|
235
|
+
execute schema_creation.accept td
|
236
|
+
end
|
237
|
+
|
212
238
|
# Drops a ClickHouse database.
|
213
239
|
def drop_database(name) #:nodoc:
|
214
|
-
sql = "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
240
|
+
sql = apply_cluster "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
215
241
|
log_with_debug(sql, adapter_name) do
|
216
242
|
res = @connection.post("/?#{@config.except(:database).to_param}", sql)
|
217
243
|
process_response(res)
|
218
244
|
end
|
219
245
|
end
|
220
246
|
|
247
|
+
def rename_table(table_name, new_name)
|
248
|
+
do_execute apply_cluster "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
249
|
+
end
|
250
|
+
|
221
251
|
def drop_table(table_name, options = {}) # :nodoc:
|
222
|
-
do_execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
|
252
|
+
do_execute apply_cluster "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
|
253
|
+
end
|
254
|
+
|
255
|
+
def cluster
|
256
|
+
@full_config[:cluster_name]
|
257
|
+
end
|
258
|
+
|
259
|
+
def replica
|
260
|
+
@full_config[:replica_name]
|
261
|
+
end
|
262
|
+
|
263
|
+
def replica_path(table)
|
264
|
+
"/clickhouse/tables/#{cluster}/#{@config[:database]}.#{table}"
|
265
|
+
end
|
266
|
+
|
267
|
+
def apply_cluster(sql)
|
268
|
+
cluster ? "#{sql} ON CLUSTER #{cluster}" : sql
|
223
269
|
end
|
224
270
|
|
225
271
|
protected
|
@@ -233,6 +279,16 @@ module ActiveRecord
|
|
233
279
|
def connect
|
234
280
|
@connection = Net::HTTP.start(@connection_parameters[0], @connection_parameters[1], use_ssl: @connection_parameters[2], verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
235
281
|
end
|
282
|
+
|
283
|
+
def apply_replica(table, options)
|
284
|
+
if replica && cluster && options[:options]
|
285
|
+
match = options[:options].match(/^(.*?MergeTree)\(([^\)]*)\)(.*?)$/)
|
286
|
+
if match
|
287
|
+
options[:options] = "Replicated#{match[1]}(#{([replica_path(table), replica].map{|v| "'#{v}'"} + [match[2].presence]).compact.join(', ')})#{match[3]}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
options
|
291
|
+
end
|
236
292
|
end
|
237
293
|
end
|
238
294
|
end
|
@@ -4,6 +4,7 @@ require 'active_record/connection_adapters/clickhouse_adapter'
|
|
4
4
|
|
5
5
|
if defined?(Rails::Railtie)
|
6
6
|
require 'clickhouse-activerecord/railtie'
|
7
|
+
require 'clickhouse-activerecord/schema'
|
7
8
|
require 'clickhouse-activerecord/schema_dumper'
|
8
9
|
require 'clickhouse-activerecord/tasks'
|
9
10
|
ActiveRecord::Tasks::DatabaseTasks.register_task(/clickhouse/, "ClickhouseActiverecord::Tasks")
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ClickhouseActiverecord
|
2
|
+
|
3
|
+
class SchemaMigration < ::ActiveRecord::SchemaMigration
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def create_table
|
7
|
+
unless table_exists?
|
8
|
+
version_options = connection.internal_string_options_for_primary_key
|
9
|
+
|
10
|
+
connection.create_table(table_name, id: false, options: 'ReplacingMergeTree(ver) PARTITION BY version ORDER BY (version)') do |t|
|
11
|
+
t.string :version, version_options
|
12
|
+
t.column :active, 'Int8', null: false, default: '1'
|
13
|
+
t.datetime :ver, null: false, default: -> { 'now()' }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def all_versions
|
19
|
+
from("#{table_name} FINAL").where(active: 1).order(:version).pluck(:version)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
25
|
+
class << self
|
26
|
+
def create_table
|
27
|
+
unless table_exists?
|
28
|
+
key_options = connection.internal_string_options_for_primary_key
|
29
|
+
|
30
|
+
connection.create_table(table_name, id: false, options: 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)') do |t|
|
31
|
+
t.string :key, key_options
|
32
|
+
t.string :value
|
33
|
+
t.timestamps
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MigrationContext < ::ActiveRecord::MigrationContext #:nodoc:
|
41
|
+
attr_reader :migrations_paths, :schema_migration
|
42
|
+
|
43
|
+
def initialize(migrations_paths, schema_migration)
|
44
|
+
@migrations_paths = migrations_paths
|
45
|
+
@schema_migration = schema_migration
|
46
|
+
end
|
47
|
+
|
48
|
+
def down(target_version = nil)
|
49
|
+
selected_migrations = if block_given?
|
50
|
+
migrations.select { |m| yield m }
|
51
|
+
else
|
52
|
+
migrations
|
53
|
+
end
|
54
|
+
|
55
|
+
ClickhouseActiverecord::Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_all_versions
|
59
|
+
if schema_migration.table_exists?
|
60
|
+
schema_migration.all_versions.map(&:to_i)
|
61
|
+
else
|
62
|
+
[]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class Migrator < ::ActiveRecord::Migrator
|
69
|
+
|
70
|
+
def initialize(direction, migrations, schema_migration, target_version = nil)
|
71
|
+
@direction = direction
|
72
|
+
@target_version = target_version
|
73
|
+
@migrated_versions = nil
|
74
|
+
@migrations = migrations
|
75
|
+
@schema_migration = schema_migration
|
76
|
+
|
77
|
+
validate(@migrations)
|
78
|
+
|
79
|
+
@schema_migration.create_table
|
80
|
+
ClickhouseActiverecord::InternalMetadata.create_table
|
81
|
+
end
|
82
|
+
|
83
|
+
def record_version_state_after_migrating(version)
|
84
|
+
if down?
|
85
|
+
migrated.delete(version)
|
86
|
+
@schema_migration.create!(version: version.to_s, active: 0)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClickhouseActiverecord
|
4
|
+
|
5
|
+
class Schema < ::ActiveRecord::Schema
|
6
|
+
|
7
|
+
def define(info, &block) # :nodoc:
|
8
|
+
instance_eval(&block)
|
9
|
+
|
10
|
+
if info[:version].present?
|
11
|
+
connection.schema_migration.create_table
|
12
|
+
connection.assume_migrated_upto_version(info[:version], ClickhouseActiverecord::Migrator.migrations_paths)
|
13
|
+
end
|
14
|
+
|
15
|
+
ClickhouseActiverecord::InternalMetadata.create_table
|
16
|
+
ClickhouseActiverecord::InternalMetadata[:environment] = connection.migration_context.current_environment
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,10 +1,127 @@
|
|
1
1
|
module ClickhouseActiverecord
|
2
2
|
class SchemaDumper < ::ActiveRecord::ConnectionAdapters::SchemaDumper
|
3
3
|
|
4
|
+
attr_accessor :simple
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base, default = false)
|
8
|
+
dumper = connection.create_schema_dumper(generate_options(config))
|
9
|
+
dumper.simple = default
|
10
|
+
dumper.dump(stream)
|
11
|
+
stream
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def header(stream)
|
18
|
+
stream.puts <<HEADER
|
19
|
+
# This file is auto-generated from the current state of the database. Instead
|
20
|
+
# of editing this file, please use the migrations feature of Active Record to
|
21
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
22
|
+
#
|
23
|
+
# This file is the source Rails uses to define your schema when running `rails
|
24
|
+
# #{simple ? 'db' : 'clickhouse'}:schema:load`. When creating a new database, `rails #{simple ? 'db' : 'clickhouse'}:schema:load` tends to
|
25
|
+
# be faster and is potentially less error prone than running all of your
|
26
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
27
|
+
# migrations use external dependencies or application code.
|
28
|
+
#
|
29
|
+
# It's strongly recommended that you check this file into your version control system.
|
30
|
+
|
31
|
+
#{simple ? 'ActiveRecord' : 'ClickhouseActiverecord'}::Schema.define(#{define_params}) do
|
32
|
+
|
33
|
+
HEADER
|
34
|
+
end
|
35
|
+
|
4
36
|
def table(table, stream)
|
5
|
-
|
6
|
-
|
7
|
-
|
37
|
+
if table.match(/^\.inner\./).nil?
|
38
|
+
unless simple
|
39
|
+
stream.puts " # TABLE: #{table}"
|
40
|
+
sql = @connection.do_system_execute("SHOW CREATE TABLE `#{table.gsub(/^\.inner\./, '')}`")['data'].try(:first).try(:first)
|
41
|
+
stream.puts " # SQL: #{sql.gsub(/ENGINE = Replicated(.*?)\('[^']+',\s*'[^']+',?\s?([^\)]*)?\)/, "ENGINE = \\1(\\2)")}" if sql
|
42
|
+
# super(table.gsub(/^\.inner\./, ''), stream)
|
43
|
+
|
44
|
+
# detect view table
|
45
|
+
match = sql.match(/^CREATE\s+(MATERIALIZED)\s+VIEW/)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Copy from original dumper
|
49
|
+
columns = @connection.columns(table)
|
50
|
+
begin
|
51
|
+
tbl = StringIO.new
|
52
|
+
|
53
|
+
# first dump primary key column
|
54
|
+
pk = @connection.primary_key(table)
|
55
|
+
|
56
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
57
|
+
|
58
|
+
unless simple
|
59
|
+
# Add materialize flag
|
60
|
+
tbl.print ', view: true' if match
|
61
|
+
tbl.print ', materialized: true' if match && match[1].presence
|
62
|
+
end
|
63
|
+
|
64
|
+
case pk
|
65
|
+
when String
|
66
|
+
tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
|
67
|
+
pkcol = columns.detect { |c| c.name == pk }
|
68
|
+
pkcolspec = column_spec_for_primary_key(pkcol)
|
69
|
+
if pkcolspec.present?
|
70
|
+
tbl.print ", #{format_colspec(pkcolspec)}"
|
71
|
+
end
|
72
|
+
when Array
|
73
|
+
tbl.print ", primary_key: #{pk.inspect}"
|
74
|
+
else
|
75
|
+
tbl.print ", id: false"
|
76
|
+
end
|
77
|
+
|
78
|
+
unless simple
|
79
|
+
table_options = @connection.table_options(table)
|
80
|
+
if table_options.present?
|
81
|
+
tbl.print ", #{format_options(table_options)}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
tbl.puts ", force: :cascade do |t|"
|
86
|
+
|
87
|
+
# then dump all non-primary key columns
|
88
|
+
columns.each do |column|
|
89
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
90
|
+
next if column.name == pk
|
91
|
+
type, colspec = column_spec(column)
|
92
|
+
tbl.print " t.#{type} #{column.name.inspect}"
|
93
|
+
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
|
94
|
+
tbl.puts
|
95
|
+
end
|
96
|
+
|
97
|
+
indexes_in_create(table, tbl)
|
98
|
+
|
99
|
+
tbl.puts " end"
|
100
|
+
tbl.puts
|
101
|
+
|
102
|
+
tbl.rewind
|
103
|
+
stream.print tbl.read
|
104
|
+
rescue => e
|
105
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
106
|
+
stream.puts "# #{e.message}"
|
107
|
+
stream.puts
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def format_options(options)
|
113
|
+
if options && options[:options]
|
114
|
+
options[:options] = options[:options].gsub(/^Replicated(.*?)\('[^']+',\s*'[^']+',?\s?([^\)]*)?\)/, "\\1(\\2)")
|
115
|
+
end
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
def format_colspec(colspec)
|
120
|
+
if simple
|
121
|
+
super.gsub(/CAST\(([^,]+),.*?\)/, "\\1")
|
122
|
+
else
|
123
|
+
super
|
124
|
+
end
|
8
125
|
end
|
9
126
|
end
|
10
127
|
end
|
@@ -12,8 +12,8 @@ class ClickhouseMigrationGenerator < ActiveRecord::Generators::MigrationGenerato
|
|
12
12
|
private
|
13
13
|
|
14
14
|
def db_migrate_path
|
15
|
-
if defined?(Rails.application) && Rails.application
|
16
|
-
configured_migrate_path
|
15
|
+
if defined?(Rails.application) && Rails.application && respond_to?(:configured_migrate_path, true)
|
16
|
+
configured_migrate_path
|
17
17
|
else
|
18
18
|
default_migrate_path
|
19
19
|
end
|
data/lib/tasks/clickhouse.rake
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
namespace :clickhouse do
|
4
4
|
|
5
|
+
task prepare_schema_migration_table: :environment do
|
6
|
+
ClickhouseActiverecord::SchemaMigration.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
|
7
|
+
end
|
8
|
+
|
9
|
+
task prepare_internal_metadata_table: :environment do
|
10
|
+
ClickhouseActiverecord::InternalMetadata.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
|
11
|
+
end
|
12
|
+
|
5
13
|
task load_config: :environment do
|
6
14
|
ENV['SCHEMA'] = "db/clickhouse_schema.rb"
|
7
15
|
ActiveRecord::Migrator.migrations_paths = ["db/migrate_clickhouse"]
|
@@ -12,16 +20,18 @@ namespace :clickhouse do
|
|
12
20
|
|
13
21
|
# todo not testing
|
14
22
|
desc 'Load database schema'
|
15
|
-
task load: :load_config do
|
16
|
-
|
23
|
+
task load: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do |t, args|
|
24
|
+
simple = ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
|
25
|
+
load("#{Rails.root}/db/clickhouse_schema#{simple}.rb")
|
17
26
|
end
|
18
27
|
|
19
28
|
desc 'Dump database schema'
|
20
|
-
task dump: :environment do
|
21
|
-
|
29
|
+
task dump: :environment do |t, args|
|
30
|
+
simple = ENV['simple'] || args[:simple] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
|
31
|
+
filename = "#{Rails.root}/db/clickhouse_schema#{simple}.rb"
|
22
32
|
File.open(filename, 'w:utf-8') do |file|
|
23
33
|
ActiveRecord::Base.establish_connection(:"#{Rails.env}_clickhouse")
|
24
|
-
ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
|
34
|
+
ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, !!simple)
|
25
35
|
end
|
26
36
|
end
|
27
37
|
|
@@ -61,7 +71,15 @@ namespace :clickhouse do
|
|
61
71
|
end
|
62
72
|
|
63
73
|
desc 'Migrate the clickhouse database'
|
64
|
-
task migrate: :load_config do
|
74
|
+
task migrate: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
|
65
75
|
Rake::Task['db:migrate'].execute
|
76
|
+
if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
|
77
|
+
Rake::Task['clickhouse:schema:dump'].execute(simple: true)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'Rollback the clickhouse database'
|
82
|
+
task rollback: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
|
83
|
+
Rake::Task['db:rollback'].execute
|
66
84
|
end
|
67
85
|
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.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Odintsov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '13.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '13.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,9 @@ files:
|
|
122
122
|
- lib/clickhouse-activerecord.rb
|
123
123
|
- lib/clickhouse-activerecord/arel/table.rb
|
124
124
|
- lib/clickhouse-activerecord/arel/visitors/to_sql.rb
|
125
|
+
- lib/clickhouse-activerecord/migration.rb
|
125
126
|
- lib/clickhouse-activerecord/railtie.rb
|
127
|
+
- lib/clickhouse-activerecord/schema.rb
|
126
128
|
- lib/clickhouse-activerecord/schema_dumper.rb
|
127
129
|
- lib/clickhouse-activerecord/tasks.rb
|
128
130
|
- lib/clickhouse-activerecord/version.rb
|