clickhouse-activerecord 0.3.11 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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