diff --git a/clickhouse-activerecord.gemspec b/clickhouse-activerecord.gemspec index 14befddf..caa4efb8 100644 --- a/clickhouse-activerecord.gemspec +++ b/clickhouse-activerecord.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency "bundler", ">= 1.13.4" - spec.add_dependency 'activerecord', '>= 5.0' + spec.add_dependency 'activerecord', '>= 5.2' spec.add_development_dependency 'bundler', '~> 1.15' spec.add_development_dependency 'rake', '~> 10.0' diff --git a/lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb b/lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb index 5f73d2aa..bfddeaae 100644 --- a/lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb +++ b/lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb @@ -5,8 +5,9 @@ module ConnectionAdapters module Clickhouse module OID # :nodoc: class BigInteger < Type::BigInteger # :nodoc: - - DEFAULT_LIMIT = 8 + def type + :big_integer + end def limit DEFAULT_LIMIT diff --git a/lib/active_record/connection_adapters/clickhouse/schema_creation.rb b/lib/active_record/connection_adapters/clickhouse/schema_creation.rb index 2851c7b8..9eeea336 100644 --- a/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +++ b/lib/active_record/connection_adapters/clickhouse/schema_creation.rb @@ -4,23 +4,24 @@ module ActiveRecord module ConnectionAdapters module Clickhouse class SchemaCreation < AbstractAdapter::SchemaCreation# :nodoc: + def visit_AddColumnDefinition(o) +"ADD COLUMN #{accept(o.column)}" end def add_column_options!(sql, options) - if options[:null] == true - sql.gsub(/\s+(.*)/, ' Nullable(\1)') - else - sql + if options[:null] || options[:null].nil? + sql.gsub!(/\s+(.*)/, ' Nullable(\1)') end + sql.gsub!(/(\sString)\(\d+\)/, '\1') + sql end def add_table_options!(create_sql, options) - if engine_sql = options[:options] - create_sql << " ENGINE=#{engine_sql}" + if options[:options].present? + create_sql << " ENGINE = #{options[:options]}" else - create_sql << " ENGINE=Log()" + create_sql << " ENGINE = Log()" end create_sql diff --git a/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb b/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb index 6ccb4160..e101dc7a 100644 --- a/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb +++ b/lib/active_record/connection_adapters/clickhouse/schema_definitions.rb @@ -4,6 +4,15 @@ module ActiveRecord module ConnectionAdapters module Clickhouse class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + + def integer(*args, **options) + if options[:limit] == 8 + args.each { |name| column(name, :big_integer, options.except(:limit)) } + else + super + end + end + end end end diff --git a/lib/active_record/connection_adapters/clickhouse/schema_statements.rb b/lib/active_record/connection_adapters/clickhouse/schema_statements.rb index de3e7656..2b68dd45 100644 --- a/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +++ b/lib/active_record/connection_adapters/clickhouse/schema_statements.rb @@ -33,6 +33,42 @@ def tables(name = nil) result['data'].flatten end + def table_options(table) + sql = do_system_execute("SHOW CREATE TABLE #{table}")['data'].try(:first).try(:first) + { options: sql.gsub(/^(?:.*?)ENGINE = (.*?)$/, '\\1') } + end + + # @todo copied from ActiveRecord::ConnectionAdapters::SchemaStatements v5.2.2 + # Why version column type of String, but insert to Integer? + def assume_migrated_upto_version(version, migrations_paths) + migrations_paths = Array(migrations_paths) + version = version.to_i + sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + + migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i) + versions = migration_context.migration_files.map do |file| + migration_context.parse_migration_filename(file).first.to_i + end + + unless migrated.include?(version) + do_execute( "INSERT INTO #{sm_table} (version) VALUES (#{quote(version.to_s)})", 'SchemaMigration', format: nil) + end + + inserting = (versions - migrated).select { |v| v < version } + if inserting.any? + if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) + raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." + end + if supports_multi_insert? + do_system_execute insert_versions_sql(inserting) + else + inserting.each do |v| + do_system_execute insert_versions_sql(v) + end + end + end + end + # Not indexes on clickhouse def indexes(table_name, name = nil) [] diff --git a/lib/active_record/connection_adapters/clickhouse_adapter.rb b/lib/active_record/connection_adapters/clickhouse_adapter.rb index c5b923ee..f9df50bc 100644 --- a/lib/active_record/connection_adapters/clickhouse_adapter.rb +++ b/lib/active_record/connection_adapters/clickhouse_adapter.rb @@ -8,6 +8,7 @@ require 'active_record/connection_adapters/clickhouse/schema_definitions' require 'active_record/connection_adapters/clickhouse/schema_creation' require 'active_record/connection_adapters/clickhouse/schema_statements' +require 'net/http' module ActiveRecord class Base @@ -15,7 +16,7 @@ class << self # Establishes a connection to the database that's used by all Active Record objects def clickhouse_connection(config) config = config.symbolize_keys - host = config[:host] + host = config[:host] || 'localhost' port = config[:port] || 8123 if config.key?(:database) @@ -124,11 +125,13 @@ def valid_type?(type) def extract_limit(sql_type) # :nodoc: case sql_type - when 'Nullable(String)' - 255 - when /Nullable\(U?Int(8|16)\)/ - 4 - when /Nullable\(U?Int(32|64)\)/ + when /(Nullable)?\(?String\)?/ + super('String') + when /(Nullable)?\(?U?Int8\)?/ + super('int2') + when /(Nullable)?\(?U?Int(16|32)\)?/ + super('int4') + when /(Nullable)?\(?U?Int(64)\)?/ 8 else super @@ -137,18 +140,17 @@ def extract_limit(sql_type) # :nodoc: def initialize_type_map(m) # :nodoc: super - register_class_with_limit m, 'String', Type::String - register_class_with_limit m, 'Nullable(String)', Type::String - register_class_with_limit m, 'Uint8', Type::UnsignedInteger + register_class_with_limit m, %r(String), Type::String register_class_with_limit m, 'Date', Clickhouse::OID::Date register_class_with_limit m, 'DateTime', Clickhouse::OID::DateTime - m.alias_type 'UInt16', 'uint4' - m.alias_type 'UInt32', 'uint8' - m.register_type 'UInt64', Clickhouse::OID::BigInteger.new - m.alias_type 'Int8', 'int4' - m.alias_type 'Int16', 'int4' - m.alias_type 'Int32', 'int8' - m.alias_type 'Int64', 'UInt64' + register_class_with_limit m, %r(Uint8), Type::UnsignedInteger + m.alias_type 'UInt16', 'UInt8' + m.alias_type 'UInt32', 'UInt8' + register_class_with_limit m, %r(UInt64), Type::UnsignedInteger + register_class_with_limit m, %r(Int8), Type::Integer + m.alias_type 'Int16', 'Int8' + m.alias_type 'Int32', 'Int8' + register_class_with_limit m, %r(Int64), Type::Integer end # Executes insert +sql+ statement in the context of this connection using @@ -163,6 +165,10 @@ def primary_key(table_name) #:nodoc: false end + def create_schema_dumper(options) # :nodoc: + ClickhouseActiverecord::SchemaDumper.create(self, options) + end + protected def last_inserted_id(result) diff --git a/lib/clickhouse-activerecord/schema_dumper.rb b/lib/clickhouse-activerecord/schema_dumper.rb index e3af8055..2639b919 100644 --- a/lib/clickhouse-activerecord/schema_dumper.rb +++ b/lib/clickhouse-activerecord/schema_dumper.rb @@ -1,5 +1,5 @@ module ClickhouseActiverecord - class SchemaDumper < ActiveRecord::SchemaDumper + class SchemaDumper < ::ActiveRecord::ConnectionAdapters::SchemaDumper def table(table, stream) stream.puts " # TABLE: #{table}" diff --git a/spec/cases/migration_spec.rb b/spec/cases/migration_spec.rb index d86219a2..c21fd8e1 100644 --- a/spec/cases/migration_spec.rb +++ b/spec/cases/migration_spec.rb @@ -61,11 +61,13 @@ current_schema = schema(model) - expect(current_schema.keys.count).to eq(2) + expect(current_schema.keys.count).to eq(3) expect(current_schema).to have_key('id') expect(current_schema).to have_key('money') + expect(current_schema).to have_key('balance') expect(current_schema['id'].sql_type).to eq('UInt32') - expect(current_schema['money'].sql_type).to eq('Decimal(16, 4)') + expect(current_schema['money'].sql_type).to eq('Nullable(Decimal(16, 4))') + expect(current_schema['balance'].sql_type).to eq('Decimal(32, 2)') end end end @@ -98,7 +100,7 @@ expect(current_schema).to have_key('new_column') expect(current_schema['id'].sql_type).to eq('UInt32') expect(current_schema['date'].sql_type).to eq('Date') - expect(current_schema['new_column'].sql_type).to eq('UInt64') + expect(current_schema['new_column'].sql_type).to eq('Nullable(UInt64)') end end diff --git a/spec/fixtures/migrations/add_sample_data/1_create_events_table.rb b/spec/fixtures/migrations/add_sample_data/1_create_events_table.rb index a39417f4..5da04c4c 100644 --- a/spec/fixtures/migrations/add_sample_data/1_create_events_table.rb +++ b/spec/fixtures/migrations/add_sample_data/1_create_events_table.rb @@ -3,8 +3,8 @@ class CreateEventsTable < ActiveRecord::Migration[5.0] def up create_table :events, options: 'MergeTree(date, (date, event_name), 8192)' do |t| - t.string :event_name - t.date :date + t.string :event_name, null: false + t.date :date, null: false end end end diff --git a/spec/fixtures/migrations/dsl_add_column/1_create_some_table.rb b/spec/fixtures/migrations/dsl_add_column/1_create_some_table.rb index de37d5e3..2435e2c6 100644 --- a/spec/fixtures/migrations/dsl_add_column/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_add_column/1_create_some_table.rb @@ -3,7 +3,7 @@ class CreateSomeTable < ActiveRecord::Migration[5.0] def up create_table :some, options: 'MergeTree(date, (date), 8192)' do |t| - t.date :date + t.date :date, null: false end end end diff --git a/spec/fixtures/migrations/dsl_drop_column/1_create_some_table.rb b/spec/fixtures/migrations/dsl_drop_column/1_create_some_table.rb index de37d5e3..2435e2c6 100644 --- a/spec/fixtures/migrations/dsl_drop_column/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_drop_column/1_create_some_table.rb @@ -3,7 +3,7 @@ class CreateSomeTable < ActiveRecord::Migration[5.0] def up create_table :some, options: 'MergeTree(date, (date), 8192)' do |t| - t.date :date + t.date :date, null: false end end end diff --git a/spec/fixtures/migrations/dsl_drop_table/1_create_some_table.rb b/spec/fixtures/migrations/dsl_drop_table/1_create_some_table.rb index de37d5e3..2435e2c6 100644 --- a/spec/fixtures/migrations/dsl_drop_table/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_drop_table/1_create_some_table.rb @@ -3,7 +3,7 @@ class CreateSomeTable < ActiveRecord::Migration[5.0] def up create_table :some, options: 'MergeTree(date, (date), 8192)' do |t| - t.date :date + t.date :date, null: false end end end diff --git a/spec/fixtures/migrations/dsl_table_with_decimal_creation/1_create_some_table.rb b/spec/fixtures/migrations/dsl_table_with_decimal_creation/1_create_some_table.rb index 068fa84b..3aedb806 100644 --- a/spec/fixtures/migrations/dsl_table_with_decimal_creation/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_table_with_decimal_creation/1_create_some_table.rb @@ -4,6 +4,7 @@ class CreateSomeTable < ActiveRecord::Migration[5.0] def up create_table :some do |t| t.decimal :money, precision: 16, scale: 4 + t.decimal :balance, precision: 32, scale: 2, null: false, default: 0 end end end diff --git a/spec/fixtures/migrations/dsl_table_with_engine_creation/1_create_some_table.rb b/spec/fixtures/migrations/dsl_table_with_engine_creation/1_create_some_table.rb index de37d5e3..2435e2c6 100644 --- a/spec/fixtures/migrations/dsl_table_with_engine_creation/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_table_with_engine_creation/1_create_some_table.rb @@ -3,7 +3,7 @@ class CreateSomeTable < ActiveRecord::Migration[5.0] def up create_table :some, options: 'MergeTree(date, (date), 8192)' do |t| - t.date :date + t.date :date, null: false end end end