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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03776772df850312a0cb6994a9797aee37fdf537eb8839d53e358fe78924b844
4
- data.tar.gz: b97fce3acd8c2f8b06cc50bd262c96db6d7355ff1317d4cab5e6271999f38098
3
+ metadata.gz: b584e48174f94997dc9dd04d3ffde3ff7c4b24535117c565e580c646f2f2581c
4
+ data.tar.gz: 9e7c01a185d23ac6e5ac16da635b118a59e7a7c7cdf86ce1c28e92aebacd3baa
5
5
  SHA512:
6
- metadata.gz: '0580fd619a922e91c09173d86c9a207a334d66e88d3876da2838f6ea7e3db92925b289591f8b15cb624ff7f7e5098aa9790c26f7a6ff475b537f2efd27b79fb7'
7
- data.tar.gz: 6dc95a06b6b0a079985b9863d41a5f1611ad479dd922449d7a612ec757b513b568d570c9272b80428398934aa958b5eecfb5c43ee4432667d5b938b5338e0319
6
+ metadata.gz: 8b3132522dbc7abaffa180049e350d4ceea82863ab3c208611c4b3227e6ebece1f706262daebec562bd13822fdcd31d44e6321ec97083543484e238e3e07ed2f
7
+ data.tar.gz: 44f3c58ff30e78b4e83066fbf9609bd656bf9c3e64f56a6113713068b98393c680ec949b5d8832da533ea6cc12216584a9fcf7b34279efc8c499ec9ff376a166
@@ -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
- Tested on ClickHouse version 18.14.
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
- We use schema for emulate development or tests environment on PostgreSQL adapter.
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', '~> 10.0'
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 #{table}")['data'].try(:first).try(:first)
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
- if ActiveRecord::version >= Gem::Version.new('6')
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 #{table_name}", table_name)
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
- if ActiveRecord::version >= Gem::Version.new('6')
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
- stream.puts " # TABLE: #{table}"
6
- stream.puts " # SQL: #{@connection.do_system_execute("SHOW CREATE TABLE #{table.gsub(/^\.inner\./, '')}")['data'].try(:first).try(:first)}"
7
- super(table.gsub(/^\.inner\./, ''), stream)
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
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '0.3.11'
2
+ VERSION = '0.4.3'
3
3
  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 || default_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
@@ -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
- load("#{Rails.root}/db/clickhouse_schema.rb")
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
- filename = "#{Rails.root}/db/clickhouse_schema.rb"
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.11
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-04-17 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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