diff --git a/README.md b/README.md
index f957e653..2c665031 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Clickhouse::Activerecord
-A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 5.2.
+A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 7.1.
Support ClickHouse version from 22.0 LTS.
## Installation
@@ -50,41 +50,31 @@ class ActionView < ActiveRecord::Base
end
```
-## Usage in Rails 5
+## Usage in Rails
Add your `database.yml` connection information with postfix `_clickhouse` for you environment:
```yml
-development_clickhouse:
+development:
adapter: clickhouse
database: database
```
-Add to your model:
+Your model example:
```ruby
class Action < ActiveRecord::Base
- establish_connection "#{Rails.env}_clickhouse".to_sym
end
```
For materialized view model add:
```ruby
class ActionView < ActiveRecord::Base
- establish_connection "#{Rails.env}_clickhouse".to_sym
self.is_view = true
end
```
-Or global connection:
-
-```yml
-development:
- adapter: clickhouse
- database: database
-```
-
-## Usage in Rails 6 with second database
+## Usage in Rails with second database
Add your `database.yml` connection information for you environment:
@@ -102,31 +92,31 @@ Connection [Multiple Databases with Active Record](https://guides.rubyonrails.or
```ruby
class Action < ActiveRecord::Base
- connects_to database: { writing: :clickhouse, reading: :clickhouse }
+ establish_connection :clickhouse
end
```
### Rake tasks
-**Note!** For Rails 6 you can use default rake tasks if you configure `migrations_paths` in your `database.yml`, for example: `rake db:migrate`
-
Create / drop / purge / reset database:
- $ rake clickhouse:create
- $ rake clickhouse:drop
- $ rake clickhouse:purge
- $ rake clickhouse:reset
+ $ rake db:create
+ $ rake db:drop
+ $ rake db:purge
+ $ rake db:reset
-Prepare system tables for rails:
+Or with multiple databases:
- $ rake clickhouse:prepare_schema_migration_table
- $ rake clickhouse:prepare_internal_metadata_table
+ $ rake db:create:clickhouse
+ $ rake db:drop:clickhouse
+ $ rake db:purge:clickhouse
+ $ rake db:reset:clickhouse
Migration:
$ rails g clickhouse_migration MIGRATION_NAME COLUMNS
- $ rake clickhouse:migrate
- $ rake clickhouse:rollback
+ $ rake db:migrate
+ $ rake db:rollback
### Dump / Load for multiple using databases
@@ -195,20 +185,20 @@ User.joins(:actions).using(:group_id)
Integer types are unsigned by default. Specify signed values with `:unsigned =>
false`. The default integer is `UInt32`
-| Type (bit size) | Range | :limit (byte size) |
-| :--- | :----: | ---: |
-| Int8 | -128 to 127 | 1 |
-| Int16 | -32768 to 32767 | 2 |
-| Int32 | -2147483648 to 2,147,483,647 | 3,4 |
-| Int64 | -9223372036854775808 to 9223372036854775807] | 5,6,7,8 |
-| Int128 | ... | 9 - 15 |
-| Int256 | ... | 16+ |
-| UInt8 | 0 to 255 | 1 |
-| UInt16 | 0 to 65,535 | 2 |
-| UInt32 | 0 to 4,294,967,295 | 3,4 |
-| UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
-| UInt256 | 0 to ... | 8+ |
-| Array | ... | ... |
+| Type (bit size) | Range | :limit (byte size) |
+|:----------------|:--------------------------------------------:|-------------------:|
+| Int8 | -128 to 127 | 1 |
+| Int16 | -32768 to 32767 | 2 |
+| Int32 | -2147483648 to 2,147,483,647 | 3,4 |
+| Int64 | -9223372036854775808 to 9223372036854775807] | 5,6,7,8 |
+| Int128 | ... | 9 - 15 |
+| Int256 | ... | 16+ |
+| UInt8 | 0 to 255 | 1 |
+| UInt16 | 0 to 65,535 | 2 |
+| UInt32 | 0 to 4,294,967,295 | 3,4 |
+| UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
+| UInt256 | 0 to ... | 8+ |
+| Array | ... | ... |
Example:
diff --git a/clickhouse-activerecord.gemspec b/clickhouse-activerecord.gemspec
index 58711d24..57fda949 100644
--- a/clickhouse-activerecord.gemspec
+++ b/clickhouse-activerecord.gemspec
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']
spec.add_runtime_dependency 'bundler', '>= 1.13.4'
- spec.add_runtime_dependency 'activerecord', '>= 5.2', '< 7'
+ spec.add_runtime_dependency 'activerecord', '>= 7.1'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.4'
diff --git a/lib/active_record/connection_adapters/clickhouse/schema_statements.rb b/lib/active_record/connection_adapters/clickhouse/schema_statements.rb
index 2fd2ff39..0d649362 100644
--- a/lib/active_record/connection_adapters/clickhouse/schema_statements.rb
+++ b/lib/active_record/connection_adapters/clickhouse/schema_statements.rb
@@ -10,13 +10,13 @@ def execute(sql, name = nil, settings: {})
do_execute(sql, name, settings: settings)
end
- def exec_insert(sql, name, _binds, _pk = nil, _sequence_name = nil)
+ def exec_insert(sql, name, _binds, _pk = nil, _sequence_name = nil, returning: nil)
new_sql = sql.dup.sub(/ (DEFAULT )?VALUES/, " VALUES")
do_execute(new_sql, name, format: nil)
true
end
- def exec_query(sql, name = nil, binds = [], prepare: false)
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false)
result = do_execute(sql, name)
ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'], result['meta'].map { |m| [m['name'], type_map.lookup(m['type'])] }.to_h)
rescue ActiveRecord::ActiveRecordError => e
@@ -137,7 +137,7 @@ def create_table_definition(table_name, **options)
Clickhouse::TableDefinition.new(self, table_name, **options)
end
- def new_column_from_field(table_name, field)
+ def new_column_from_field(table_name, field, _definitions)
sql_type = field[1]
type_metadata = fetch_type_metadata(sql_type)
default = field[3]
diff --git a/lib/active_record/connection_adapters/clickhouse_adapter.rb b/lib/active_record/connection_adapters/clickhouse_adapter.rb
index 48186107..607c87f2 100644
--- a/lib/active_record/connection_adapters/clickhouse_adapter.rb
+++ b/lib/active_record/connection_adapters/clickhouse_adapter.rb
@@ -136,7 +136,11 @@ def initialize(logger, connection_parameters, config, full_config)
# Support SchemaMigration from v5.2.2 to v6+
def schema_migration # :nodoc:
- ClickhouseActiverecord::SchemaMigration
+ ClickhouseActiverecord::SchemaMigration.new(self)
+ end
+
+ def internal_metadata # :nodoc:
+ ClickhouseActiverecord::InternalMetadata.new(self)
end
def migrations_paths
@@ -144,7 +148,7 @@ def migrations_paths
end
def migration_context # :nodoc:
- ClickhouseActiverecord::MigrationContext.new(migrations_paths, schema_migration)
+ ClickhouseActiverecord::MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
end
def arel_visitor # :nodoc:
@@ -159,66 +163,73 @@ def valid_type?(type)
!native_database_types[type].nil?
end
- def extract_limit(sql_type) # :nodoc:
- case sql_type
- when /(Nullable)?\(?String\)?/
- super('String')
- when /(Nullable)?\(?U?Int8\)?/
- 1
- when /(Nullable)?\(?U?Int16\)?/
- 2
- when /(Nullable)?\(?U?Int32\)?/
- nil
- when /(Nullable)?\(?U?Int64\)?/
- 8
- else
- super
+ class << self
+ def extract_limit(sql_type) # :nodoc:
+ case sql_type
+ when /(Nullable)?\(?String\)?/
+ super('String')
+ when /(Nullable)?\(?U?Int8\)?/
+ 1
+ when /(Nullable)?\(?U?Int16\)?/
+ 2
+ when /(Nullable)?\(?U?Int32\)?/
+ nil
+ when /(Nullable)?\(?U?Int64\)?/
+ 8
+ else
+ super
+ end
end
- end
- # `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
- # except this permits a space after the comma
+ # `extract_scale` and `extract_precision` are the same as in the Rails abstract base class,
+ # except this permits a space after the comma
- def extract_scale(sql_type)
- case sql_type
- when /\((\d+)\)/ then 0
- when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
+ def extract_scale(sql_type)
+ case sql_type
+ when /\((\d+)\)/ then 0
+ when /\((\d+)(,\s?(\d+))\)/ then $3.to_i
+ end
end
- end
- def extract_precision(sql_type)
- $1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
- end
-
- def initialize_type_map(m) # :nodoc:
- super
- register_class_with_limit m, %r(String), Type::String
- register_class_with_limit m, 'Date', Clickhouse::OID::Date
- register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
-
- register_class_with_limit m, %r(Int8), Type::Integer
- register_class_with_limit m, %r(Int16), Type::Integer
- register_class_with_limit m, %r(Int32), Type::Integer
- register_class_with_limit m, %r(Int64), Type::Integer
- register_class_with_limit m, %r(Int128), Type::Integer
- register_class_with_limit m, %r(Int256), Type::Integer
-
- register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
- register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
- register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
- register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
- #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
- register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
- # register_class_with_limit m, %r(Array), Clickhouse::OID::Array
- m.register_type(%r(Array)) do |sql_type|
- Clickhouse::OID::Array.new(sql_type)
+ def extract_precision(sql_type)
+ $1.to_i if sql_type =~ /\((\d+)(,\s?\d+)?\)/
end
+
+ def initialize_type_map(m) # :nodoc:
+ super
+ register_class_with_limit m, %r(String), Type::String
+ register_class_with_limit m, 'Date', Clickhouse::OID::Date
+ register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
+
+ register_class_with_limit m, %r(Int8), Type::Integer
+ register_class_with_limit m, %r(Int16), Type::Integer
+ register_class_with_limit m, %r(Int32), Type::Integer
+ register_class_with_limit m, %r(Int64), Type::Integer
+ register_class_with_limit m, %r(Int128), Type::Integer
+ register_class_with_limit m, %r(Int256), Type::Integer
+
+ register_class_with_limit m, %r(UInt8), Type::UnsignedInteger
+ register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
+ register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
+ register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
+ #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
+ register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
+ # register_class_with_limit m, %r(Array), Clickhouse::OID::Array
+ m.register_type(%r(Array)) do |sql_type|
+ Clickhouse::OID::Array.new(sql_type)
+ end
+ end
+ end
+
+ # In Rails 7 used constant TYPE_MAP, we need redefine method
+ def type_map
+ @type_map ||= Type::TypeMap.new.tap { |m| ClickhouseAdapter.initialize_type_map(m) }
end
- def _quote(value)
+ def quote(value)
case value
when Array
- '[' + value.map { |v| _quote(v) }.join(', ') + ']'
+ '[' + value.map { |v| quote(v) }.join(', ') + ']'
else
super
end
diff --git a/lib/arel/visitors/clickhouse.rb b/lib/arel/visitors/clickhouse.rb
index c59b4b47..a63471a9 100644
--- a/lib/arel/visitors/clickhouse.rb
+++ b/lib/arel/visitors/clickhouse.rb
@@ -15,7 +15,7 @@ def aggregate(name, o, collector)
def visit_Arel_Table o, collector
collector = super
- collector << ' FINAL ' if o.final
+ collector << ' FINAL' if o.final
collector
end
diff --git a/lib/clickhouse-activerecord/migration.rb b/lib/clickhouse-activerecord/migration.rb
index b208e848..330bdfaa 100644
--- a/lib/clickhouse-activerecord/migration.rb
+++ b/lib/clickhouse-activerecord/migration.rb
@@ -3,107 +3,96 @@
module ClickhouseActiverecord
class SchemaMigration < ::ActiveRecord::SchemaMigration
- class << self
+ def create_table
+ return if table_exists?
- def create_table
- return if table_exists?
+ version_options = connection.internal_string_options_for_primary_key
+ table_options = {
+ id: false, options: 'ReplacingMergeTree(ver) ORDER BY (version)', if_not_exists: true
+ }
+ full_config = connection.instance_variable_get(:@full_config) || {}
- version_options = connection.internal_string_options_for_primary_key
- table_options = {
- id: false, options: 'ReplacingMergeTree(ver) ORDER BY (version)', if_not_exists: true
- }
- full_config = connection.instance_variable_get(:@full_config) || {}
+ if full_config[:distributed_service_tables]
+ table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(version)')
- if full_config[:distributed_service_tables]
- table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(version)')
-
- distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
- end
-
- connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
- t.string :version, **version_options
- t.column :active, 'Int8', null: false, default: '1'
- t.datetime :ver, null: false, default: -> { 'now()' }
- end
+ distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
end
- def all_versions
- final.where(active: 1).order(:version).pluck(:version)
+ connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
+ t.string :version, **version_options
+ t.column :active, 'Int8', null: false, default: '1'
+ t.datetime :ver, null: false, default: -> { 'now()' }
end
end
- end
- class InternalMetadata < ::ActiveRecord::InternalMetadata
- class << self
+ def versions
+ table = arel_table.dup
+ table.final = true
+ sm = Arel::SelectManager.new(table)
+ sm.project(arel_table[primary_key])
+ sm.order(arel_table[primary_key].asc)
+ sm.where([arel_table['active'].eq(1)])
- def []=(key, value)
- row = final.find_by(key: key)
- if row.nil? || row.value != value
- create!(key: key, value: value)
- end
- end
+ connection.select_values(sm, "#{self.class} Load")
+ end
- def [](key)
- final.where(key: key).pluck(:value).first
- end
+ def delete_version(version)
+ im = Arel::InsertManager.new(arel_table)
+ im.insert(arel_table[primary_key] => version.to_s, arel_table['active'] => 0)
+ connection.insert(im, "#{self.class} Create Rollback Version", primary_key, version)
+ end
+ end
- def create_table
- return if table_exists?
+ class InternalMetadata < ::ActiveRecord::InternalMetadata
- key_options = connection.internal_string_options_for_primary_key
- table_options = {
- id: false,
- options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
- if_not_exists: true
- }
- full_config = connection.instance_variable_get(:@full_config) || {}
+ def create_table
+ return if table_exists? || !enabled?
- if full_config[:distributed_service_tables]
- table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
+ key_options = connection.internal_string_options_for_primary_key
+ table_options = {
+ id: false,
+ options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
+ if_not_exists: true
+ }
+ full_config = connection.instance_variable_get(:@full_config) || {}
- distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
- end
+ if full_config[:distributed_service_tables]
+ table_options.merge!(with_distributed: table_name, sharding_key: 'cityHash64(created_at)')
- connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
- t.string :key, **key_options
- t.string :value
- t.timestamps
- end
+ distributed_suffix = "_#{full_config[:distributed_service_tables_suffix] || 'distributed'}"
end
- end
- end
- class MigrationContext < ::ActiveRecord::MigrationContext #:nodoc:
- attr_reader :migrations_paths, :schema_migration
-
- def initialize(migrations_paths, schema_migration)
- @migrations_paths = migrations_paths
- @schema_migration = schema_migration
+ connection.create_table(table_name + distributed_suffix.to_s, **table_options) do |t|
+ t.string :key, **key_options
+ t.string :value
+ t.timestamps
+ end
end
- def up(target_version = nil)
- selected_migrations = if block_given?
- migrations.select { |m| yield m }
- else
- migrations
- end
+ private
- ClickhouseActiverecord::Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
+ def update_entry(key, new_value)
+ create_entry(key, new_value)
end
- def down(target_version = nil)
- selected_migrations = if block_given?
- migrations.select { |m| yield m }
- else
- migrations
- end
+ def select_entry(key)
+ table = arel_table.dup
+ table.final = true
+ sm = Arel::SelectManager.new(table)
+ sm.project(Arel::Nodes::SqlLiteral.new("*"))
+ sm.where(table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
+ sm.order(table[primary_key].asc)
+ sm.limit = 1
- ClickhouseActiverecord::Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
+ connection.select_all(sm, "#{self.class} Load").first
end
+ end
+
+ class MigrationContext < ::ActiveRecord::MigrationContext #:nodoc:
def get_all_versions
if schema_migration.table_exists?
- schema_migration.all_versions.map(&:to_i)
+ schema_migration.versions.map(&:to_i)
else
[]
end
@@ -111,36 +100,4 @@ def get_all_versions
end
- class Migrator < ::ActiveRecord::Migrator
-
- def initialize(direction, migrations, schema_migration, target_version = nil)
- @direction = direction
- @target_version = target_version
- @migrated_versions = nil
- @migrations = migrations
- @schema_migration = schema_migration
-
- validate(@migrations)
-
- @schema_migration.create_table
- ClickhouseActiverecord::InternalMetadata.create_table
- end
-
- def record_version_state_after_migrating(version)
- if down?
- migrated.delete(version)
- @schema_migration.create!(version: version.to_s, active: 0)
- else
- super
- end
- end
-
- private
-
- def record_environment
- return if down?
- ClickhouseActiverecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
- end
-
- end
end
diff --git a/lib/clickhouse-activerecord/tasks.rb b/lib/clickhouse-activerecord/tasks.rb
index f3e20033..cd1890b9 100644
--- a/lib/clickhouse-activerecord/tasks.rb
+++ b/lib/clickhouse-activerecord/tasks.rb
@@ -2,7 +2,6 @@
module ClickhouseActiverecord
class Tasks
-
delegate :connection, :establish_connection, :clear_active_connections!, to: ActiveRecord::Base
def initialize(configuration)
@@ -11,7 +10,7 @@ def initialize(configuration)
def create
establish_master_connection
- connection.create_database @configuration["database"]
+ connection.create_database @configuration['database']
rescue ActiveRecord::StatementInvalid => e
if e.cause.to_s.include?('already exists')
raise ActiveRecord::DatabaseAlreadyExists
@@ -22,7 +21,7 @@ def create
def drop
establish_master_connection
- connection.drop_database @configuration["database"]
+ connection.drop_database @configuration['database']
end
def purge
diff --git a/lib/clickhouse-activerecord/version.rb b/lib/clickhouse-activerecord/version.rb
index 901480fe..fd3b523d 100644
--- a/lib/clickhouse-activerecord/version.rb
+++ b/lib/clickhouse-activerecord/version.rb
@@ -1,3 +1,3 @@
module ClickhouseActiverecord
- VERSION = '0.6.1'
+ VERSION = '1.0.0'
end
diff --git a/lib/core_extensions/active_record/relation.rb b/lib/core_extensions/active_record/relation.rb
index 7a938d1d..35c7a50d 100644
--- a/lib/core_extensions/active_record/relation.rb
+++ b/lib/core_extensions/active_record/relation.rb
@@ -11,21 +11,65 @@ def reverse_order!
self
end
+ # 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.
+ # For example:
+ #
+ # users = User.settings(optimize_read_in_order: 1, cast_keep_nullable: 1).where(name: 'John')
+ # # SELECT users.* FROM users WHERE users.name = 'John' SETTINGS optimize_read_in_order = 1, cast_keep_nullable = 1
+ #
+ # An ActiveRecord::ActiveRecordError will be raised if database not ClickHouse.
# @param [Hash] opts
def settings(**opts)
+ spawn.settings!(**opts)
+ end
+
+ # @param [Hash] opts
+ def settings!(**opts)
+ assert_mutability!
check_command('SETTINGS')
@values[:settings] = (@values[:settings] || {}).merge opts
self
end
+ # 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.
+ # For example:
+ #
+ # users = User.final.all
+ # # SELECT users.* FROM users FINAL
+ #
+ # An ActiveRecord::ActiveRecordError will be raised if database not ClickHouse.
# @param [Boolean] final
def final(final = true)
+ spawn.final!(final)
+ end
+
+ # @param [Boolean] final
+ def final!(final = true)
+ assert_mutability!
check_command('FINAL')
@table = @table.dup
@table.final = final
self
end
+ # The USING clause specifies one or more columns to join, which establishes the equality of these columns. For example:
+ #
+ # users = User.joins(:joins).using(:event_name, :date)
+ # # SELECT users.* FROM users INNER JOIN joins USING event_name,date
+ #
+ # An ActiveRecord::ActiveRecordError will be raised if database not ClickHouse.
+ # @param [Array] opts
+ def using(*opts)
+ spawn.using!(*opts)
+ end
+
+ # @param [Array] opts
+ def using!(*opts)
+ assert_mutability!
+ @values[:using] = opts
+ self
+ end
+
private
def check_command(cmd)
@@ -36,6 +80,7 @@ def build_arel(aliases = nil)
arel = super
arel.settings(@values[:settings]) if @values[:settings].present?
+ arel.using(@values[:using]) if @values[:using].present?
arel
end
diff --git a/lib/core_extensions/arel/nodes/select_statement.rb b/lib/core_extensions/arel/nodes/select_statement.rb
index d7f3807e..b8f26ab5 100644
--- a/lib/core_extensions/arel/nodes/select_statement.rb
+++ b/lib/core_extensions/arel/nodes/select_statement.rb
@@ -4,7 +4,7 @@ module Nodes
module SelectStatement
attr_accessor :settings
- def initialize
+ def initialize(relation = nil)
super
@settings = nil
end
diff --git a/lib/tasks/clickhouse.rake b/lib/tasks/clickhouse.rake
index 649ff348..181ac42c 100644
--- a/lib/tasks/clickhouse.rake
+++ b/lib/tasks/clickhouse.rake
@@ -1,86 +1,87 @@
# frozen_string_literal: true
namespace :clickhouse do
-
task prepare_schema_migration_table: :environment do
- ClickhouseActiverecord::SchemaMigration.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
+ connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection
+ connection.schema_migration.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
end
task prepare_internal_metadata_table: :environment do
- ClickhouseActiverecord::InternalMetadata.create_table unless ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any?
- end
-
- task load_config: :environment do
- ENV['SCHEMA'] = "db/clickhouse_schema.rb"
- ActiveRecord::Migrator.migrations_paths = ["db/migrate_clickhouse"]
- ActiveRecord::Base.establish_connection(:"#{Rails.env}_clickhouse")
+ connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection
+ connection.internal_metadata.create_table unless ENV['simple'] || ARGV.any? { |a| a.include?('--simple') }
end
namespace :schema do
-
- # todo not testing
+ # TODO: not testing
desc 'Load database schema'
- task load: [:load_config, :prepare_internal_metadata_table] do |t, args|
- simple = ENV['simple'] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
+ task load: %i[load_config prepare_internal_metadata_table] do
+ simple = ENV['simple'] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
ClickhouseActiverecord::SchemaMigration.drop_table
- load("#{Rails.root}/db/clickhouse_schema#{simple}.rb")
+ load(Rails.root.join("db/clickhouse_schema#{simple}.rb"))
end
desc 'Dump database schema'
- task dump: :environment do |t, args|
- simple = ENV['simple'] || args[:simple] || ARGV.map{|a| a.include?('--simple') ? true : nil}.compact.any? ? '_simple' : nil
- filename = "#{Rails.root}/db/clickhouse_schema#{simple}.rb"
+ task dump: :environment do |_, args|
+ simple = ENV['simple'] || args[:simple] || ARGV.any? { |a| a.include?('--simple') } ? '_simple' : nil
+ filename = Rails.root.join("db/clickhouse_schema#{simple}.rb")
File.open(filename, 'w:utf-8') do |file|
- ActiveRecord::Base.establish_connection(:"#{Rails.env}_clickhouse")
- ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, !!simple)
+ ActiveRecord::Base.establish_connection(:clickhouse)
+ ClickhouseActiverecord::SchemaDumper.dump(ActiveRecord::Base.connection, file, ActiveRecord::Base, simple.present?)
end
end
-
end
namespace :structure do
desc 'Load database structure'
- task load: [:load_config, 'db:check_protected_environments'] do
- ClickhouseActiverecord::Tasks.new(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"]).structure_load("#{Rails.root}/db/clickhouse_structure.sql")
+ task load: ['db:check_protected_environments'] do
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
+ ClickhouseActiverecord::Tasks.new(config).structure_load(Rails.root.join('db/clickhouse_structure.sql'))
end
desc 'Dump database structure'
- task dump: [:load_config, 'db:check_protected_environments'] do
- ClickhouseActiverecord::Tasks.new(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"]).structure_dump("#{Rails.root}/db/clickhouse_structure.sql")
+ task dump: ['db:check_protected_environments'] do
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
+ ClickhouseActiverecord::Tasks.new(config).structure_dump(Rails.root.join('db/clickhouse_structure.sql'))
end
end
desc 'Creates the database from DATABASE_URL or config/database.yml'
- task create: [:load_config] do
- ActiveRecord::Tasks::DatabaseTasks.create(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
+ task create: [] do
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
+ ActiveRecord::Tasks::DatabaseTasks.create(config)
end
desc 'Drops the database from DATABASE_URL or config/database.yml'
- task drop: [:load_config, 'db:check_protected_environments'] do
- ActiveRecord::Tasks::DatabaseTasks.drop(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
+ task drop: ['db:check_protected_environments'] do
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
+ ActiveRecord::Tasks::DatabaseTasks.drop(config)
end
desc 'Empty the database from DATABASE_URL or config/database.yml'
- task purge: [:load_config, 'db:check_protected_environments'] do
- ActiveRecord::Tasks::DatabaseTasks.purge(ActiveRecord::Base.configurations["#{Rails.env}_clickhouse"])
+ task purge: ['db:check_protected_environments'] do
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'clickhouse')
+ ActiveRecord::Tasks::DatabaseTasks.purge(config)
end
# desc 'Resets your database using your migrations for the current environment'
- task reset: :load_config do
+ task :reset do
Rake::Task['clickhouse:purge'].execute
Rake::Task['clickhouse:migrate'].execute
end
desc 'Migrate the clickhouse database'
- task migrate: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
- Rake::Task['db:migrate'].execute
+ task migrate: %i[prepare_schema_migration_table prepare_internal_metadata_table] do
+ Rake::Task['db:migrate:clickhouse'].execute
if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
Rake::Task['clickhouse:schema:dump'].execute(simple: true)
end
end
desc 'Rollback the clickhouse database'
- task rollback: [:load_config, :prepare_schema_migration_table, :prepare_internal_metadata_table] do
- Rake::Task['db:rollback'].execute
+ task rollback: %i[prepare_schema_migration_table prepare_internal_metadata_table] do
+ Rake::Task['db:rollback:clickhouse'].execute
+ if File.exists? "#{Rails.root}/db/clickhouse_schema_simple.rb"
+ Rake::Task['clickhouse:schema:dump'].execute(simple: true)
+ end
end
end
diff --git a/spec/cases/migration_spec.rb b/spec/cases/migration_spec.rb
index 7777353f..6485ad87 100644
--- a/spec/cases/migration_spec.rb
+++ b/spec/cases/migration_spec.rb
@@ -7,6 +7,9 @@
self.table_name = 'some'
end
end
+ let(:directory) { raise 'NotImplemented' }
+ let(:migrations_dir) { File.join(FIXTURES_PATH, 'migrations', directory) }
+ let(:migration_context) { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration, model.connection.internal_metadata) }
if ActiveRecord::version >= Gem::Version.new('6.1')
connection_config = ActiveRecord::Base.connection_db_config.configuration_hash
@@ -14,11 +17,16 @@
connection_config = ActiveRecord::Base.connection_config
end
+ subject do
+ quietly { migration_context.up }
+ end
+
context 'table creation' do
context 'plain' do
+ let(:directory) { 'plain_table_creation' }
+
it 'creates a table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'plain_table_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -32,9 +40,9 @@
context 'dsl' do
context 'empty' do
+ let(:directory) { 'dsl_table_creation' }
it 'creates a table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -45,9 +53,9 @@
end
context 'with engine' do
+ let(:directory) { 'dsl_table_with_engine_creation' }
it 'creates a table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_engine_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -61,9 +69,9 @@
context 'types' do
context 'decimal' do
+ let(:directory) { 'dsl_table_with_decimal_creation' }
it 'creates a table with valid scale and precision' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_decimal_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -78,9 +86,9 @@
end
context 'uuid' do
+ let(:directory) { 'dsl_table_with_uuid_creation' }
it 'creates a table with uuid columns' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_uuid_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -93,9 +101,9 @@
end
context 'datetime' do
+ let(:directory) { 'dsl_table_with_datetime_creation' }
it 'creates a table with datetime columns' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_datetime_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -108,9 +116,9 @@
end
context 'low_cardinality' do
+ let(:directory) { 'dsl_table_with_low_cardinality_creation' }
it 'creates a table with low cardinality columns' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_low_cardinality_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -125,9 +133,9 @@
end
context 'fixed_string' do
+ let(:directory) { 'dsl_table_with_fixed_string_creation' }
it 'creates a table with fixed string columns' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_fixed_string_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -140,9 +148,9 @@
end
context 'enum' do
+ let(:directory) { 'dsl_table_with_enum_creation' }
it 'creates a table with enum columns' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_table_with_enum_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -166,11 +174,9 @@
ActiveRecord::Base.establish_connection(connection_config)
end
+ let(:directory) { 'plain_table_creation' }
it 'raise error' do
- expect {
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'plain_table_creation')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
- }.to raise_error(ActiveRecord::NoDatabaseError)
+ expect { subject }.to raise_error(ActiveRecord::NoDatabaseError)
end
end
@@ -189,9 +195,9 @@
ActiveRecord::Base.establish_connection(connection_config)
end
+ let(:directory) { 'dsl_create_table_with_distributed' }
it 'creates a table with distributed table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_table_with_distributed')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
current_schema = schema(model)
current_schema_distributed = schema(model_distributed)
@@ -207,14 +213,13 @@
end
it 'drops a table with distributed table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_table_with_distributed')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
expect(ActiveRecord::Base.connection.tables).to include('some')
expect(ActiveRecord::Base.connection.tables).to include('some_distributed')
quietly do
- ClickhouseClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).down
+ migration_context.down
end
expect(ActiveRecord::Base.connection.tables).not_to include('some')
@@ -222,22 +227,24 @@
end
end
- context 'view' do
+ context 'creates a view' do
+ let(:directory) { 'dsl_create_view_with_to_section' }
it 'creates a view' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_view_with_to_section')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
expect(ActiveRecord::Base.connection.tables).to include('some_view')
end
+ end
+ context 'drops a view' do
+ let(:directory) { 'dsl_create_view_without_to_section' }
it 'drops a view' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_view_without_to_section')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
expect(ActiveRecord::Base.connection.tables).to include('some_view')
quietly do
- ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).down
+ migration_context.down
end
expect(ActiveRecord::Base.connection.tables).not_to include('some_view')
@@ -265,9 +272,9 @@
ActiveRecord::Base.establish_connection(connection_config)
end
+ let(:directory) { 'dsl_create_table_with_cluster_name_alias' }
it 'creates a table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_table_with_cluster_name_alias')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
current_schema = schema(model)
@@ -277,13 +284,12 @@
end
it 'drops a table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_create_table_with_cluster_name_alias')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).up }
+ subject
expect(ActiveRecord::Base.connection.tables).to include('some')
quietly do
- ClickhouseClickhouseActiverecord::MigrationContext.new(migrations_dir, ClickhouseActiverecord::SchemaMigration).down
+ migration_context.down
end
expect(ActiveRecord::Base.connection.tables).not_to include('some')
@@ -292,13 +298,13 @@
end
describe 'drop table' do
+ let(:directory) { 'dsl_drop_table' }
it 'drops table' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_drop_table')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up(1) }
+ quietly { migration_context.up(1) }
expect(ActiveRecord::Base.connection.tables).to include('some')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up(2) }
+ quietly { migration_context.up(2) }
expect(ActiveRecord::Base.connection.tables).not_to include('some')
end
@@ -318,9 +324,9 @@
end
describe 'add column' do
+ let(:directory) { 'dsl_add_column' }
it 'adds a new column' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_add_column')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
@@ -335,9 +341,9 @@
end
describe 'drop column' do
+ let(:directory) { 'dsl_drop_column' }
it 'drops column' do
- migrations_dir = File.join(FIXTURES_PATH, 'migrations', 'dsl_drop_column')
- quietly { ClickhouseActiverecord::MigrationContext.new(migrations_dir, model.connection.schema_migration).up }
+ subject
current_schema = schema(model)
diff --git a/spec/cases/model_spec.rb b/spec/cases/model_spec.rb
index e6e440de..fcf8b323 100644
--- a/spec/cases/model_spec.rb
+++ b/spec/cases/model_spec.rb
@@ -137,6 +137,7 @@ class Model < ActiveRecord::Base
it 'select' do
expect(model.count).to eq(2)
expect(model.final.count).to eq(1)
+ expect(model.final.where(date: '2023-07-21').to_sql).to eq('SELECT sample.* FROM sample FINAL WHERE sample.date = \'2023-07-21\'')
end
end
end