diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index c6dc7db2e8b7b..3b37b001f7487 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -141,6 +141,8 @@ module Tasks extend ActiveSupport::Autoload autoload :DatabaseTasks + autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks' + autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks' end autoload :TestCase diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b44d479fb6a2e..662c84bfdabde 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -59,36 +59,7 @@ db_namespace = namespace :db do rescue case config['adapter'] when /mysql/ - if config['adapter'] =~ /jdbc/ - #FIXME After Jdbcmysql gives this class - require 'active_record/railties/jdbcmysql_error' - error_class = ArJdbcMySQL::Error - else - error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error - end - access_denied_error = 1045 - begin - ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) - ActiveRecord::Base.establish_connection(config) - rescue error_class => sqlerr - if sqlerr.errno == access_denied_error - print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>" - root_password = $stdin.gets.strip - grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \ - "TO '#{config['username']}'@'localhost' " \ - "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;" - ActiveRecord::Base.establish_connection(config.merge( - 'database' => nil, 'username' => 'root', 'password' => root_password)) - ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) - ActiveRecord::Base.connection.execute grant_statement - ActiveRecord::Base.establish_connection(config) - else - $stderr.puts sqlerr.error - $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}" - $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] - end - end + ActiveRecord::Tasks::DatabaseTasks.create config when /postgresql/ @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8' begin diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index a72ec0175b2c1..d0d3ea12dd7b1 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,14 +1,19 @@ class ActiveRecord::Tasks::DatabaseTasks - def self.create(configuration) - if File.exist?(configuration['database']) - $stderr.puts "#{configuration['database']} already exists" - return - end + TASKS_PATTERNS = { + /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks, + # /postgresql/ => ActiveRecord::Tasks::PostgreSQLTasker, + /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks + } - ActiveRecord::Base.establish_connection(configuration) - ActiveRecord::Base.connection + def self.create(configuration) + class_for_adapter(configuration['adapter']).new(configuration).create rescue Exception => e $stderr.puts e, *(e.backtrace) $stderr.puts "Couldn't create database for #{configuration.inspect}" end + + def self.class_for_adapter(adapter) + key = TASKS_PATTERNS.keys.detect { |key| adapter[key] } + TASKS_PATTERNS[key] + end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb new file mode 100644 index 0000000000000..16d606d6fb9f8 --- /dev/null +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,76 @@ +class ActiveRecord::Tasks::MySQLDatabaseTasks + DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8' + DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci' + ACCESS_DENIED_ERROR = 1045 + + delegate :connection, :establish_connection, :to => ActiveRecord::Base + + def initialize(configuration) + @configuration = configuration + end + + def create + establish_connection configuration_without_database + connection.create_database configuration['database'], creation_options + establish_connection configuration + rescue error_class => error + raise error unless error.errno == ACCESS_DENIED_ERROR + + $stdout.print error.error + establish_connection root_configuration_without_database + connection.create_database configuration['database'], creation_options + connection.execute grant_statement.gsub(/\s+/, ' ').strip + establish_connection configuration + rescue error_class => error + $stderr.puts error.error + $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" + $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset'] + end + + private + + attr_reader :configuration + + def configuration_without_database + configuration.merge('database' => nil) + end + + def creation_options + { + :charset => (configuration['charset'] || DEFAULT_CHARSET), + :collation => (configuration['collation'] || DEFAULT_COLLATION) + } + end + + def error_class + case configuration['adapter'] + when /jdbc/ + require 'active_record/railties/jdbcmysql_error' + error_class = ArJdbcMySQL::Error + when /mysql2/ + Mysql2::Error + else + Mysql::Error + end + end + + def grant_statement + <<-SQL +GRANT ALL PRIVILEGES ON #{configuration['database']}.* + TO '#{configuration['username']}'@'localhost' +IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; + SQL + end + + def root_configuration_without_database + configuration_without_database.merge( + 'username' => 'root', + 'password' => root_password + ) + end + + def root_password + $stdout.print "Please provide the root password for your mysql installation\n>" + $stdin.gets.strip + end +end diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb new file mode 100644 index 0000000000000..882a4f98a9950 --- /dev/null +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,19 @@ +class ActiveRecord::Tasks::SQLiteDatabaseTasks + def initialize(configuration) + @configuration = configuration + end + + def create + if File.exist?(configuration['database']) + $stderr.puts "#{configuration['database']} already exists" + return + end + + ActiveRecord::Base.establish_connection(configuration) + ActiveRecord::Base.connection + end + + private + + attr_reader :configuration +end diff --git a/activerecord/test/cases/mysql_rake_test.rb b/activerecord/test/cases/mysql_rake_test.rb new file mode 100644 index 0000000000000..fc49a16553c75 --- /dev/null +++ b/activerecord/test/cases/mysql_rake_test.rb @@ -0,0 +1,120 @@ +require 'cases/helper' + +module ActiveRecord + class MysqlDBCreateTest < ActiveRecord::TestCase + def setup + @connection = stub(:create_database => true) + @configuration = { + 'adapter' => 'mysql', + 'database' => 'my-app-db' + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_establishes_connection_without_database + ActiveRecord::Base.expects(:establish_connection). + with('adapter' => 'mysql', 'database' => nil) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_creates_database_with_default_options + @connection.expects(:create_database). + with('my-app-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'}) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_creates_database_with_given_options + @connection.expects(:create_database). + with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'}) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge( + 'charset' => 'latin', 'collation' => 'latin_ci' + ) + end + + def test_establishes_connection_to_database + ActiveRecord::Base.expects(:establish_connection).with(@configuration) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + end + + class MysqlDBCreateAsRootTest < ActiveRecord::TestCase + def setup + require 'mysql' + + @connection = stub(:create_database => true, :execute => true) + @error = Mysql::Error.new "Invalid permissions" + @configuration = { + 'adapter' => 'mysql', + 'database' => 'my-app-db', + 'username' => 'pat', + 'password' => 'wossname' + } + + $stdin.stubs(:gets).returns("secret\n") + $stdout.stubs(:print).returns(nil) + @error.stubs(:errno).returns(1045) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).raises(@error).then. + returns(true) + end + + def test_root_password_is_requested + $stdin.expects(:gets).returns("secret\n") + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_connection_established_as_root + ActiveRecord::Base.expects(:establish_connection).with({ + 'adapter' => 'mysql', + 'database' => nil, + 'username' => 'root', + 'password' => 'secret' + }) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_database_created_by_root + @connection.expects(:create_database). + with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci') + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_grant_privileges_for_normal_user + @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;") + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_connection_established_as_normal_user + ActiveRecord::Base.expects(:establish_connection).returns do + ActiveRecord::Base.expects(:establish_connection).with({ + 'adapter' => 'mysql', + 'database' => 'my-app-db', + 'username' => 'pat', + 'password' => 'secret' + }) + + raise @error + end + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + + def test_sends_output_to_stderr_when_other_errors + @error.stubs(:errno).returns(42) + + $stderr.expects(:puts).at_least_once.returns(nil) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + end +end