diff options
Diffstat (limited to 'activerecord/lib/active_record/tasks')
4 files changed, 380 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb new file mode 100644 index 0000000000..b41cc68b6a --- /dev/null +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -0,0 +1,122 @@ +module ActiveRecord + module Tasks # :nodoc: + module DatabaseTasks # :nodoc: + extend self + + LOCAL_HOSTS = ['127.0.0.1', 'localhost'] + + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks) + register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks) + register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks) + + def create(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).create + rescue Exception => error + $stderr.puts error, *(error.backtrace) + $stderr.puts "Couldn't create database for #{configuration.inspect}" + end + + def create_all + each_local_configuration { |configuration| create configuration } + end + + def create_current(environment = Rails.env) + each_current_configuration(environment) { |configuration| + create configuration + } + ActiveRecord::Base.establish_connection environment + end + + def drop(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).drop + rescue Exception => error + $stderr.puts error, *(error.backtrace) + $stderr.puts "Couldn't drop #{configuration['database']}" + end + + def drop_all + each_local_configuration { |configuration| drop configuration } + end + + def drop_current(environment = Rails.env) + each_current_configuration(environment) { |configuration| + drop configuration + } + end + + def charset_current(environment = Rails.env) + charset ActiveRecord::Base.configurations[environment] + end + + def charset(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).charset + end + + def collation_current(environment = Rails.env) + collation ActiveRecord::Base.configurations[environment] + end + + def collation(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).collation + end + + def purge(configuration) + class_for_adapter(configuration['adapter']).new(configuration).purge + end + + def structure_dump(*arguments) + configuration = arguments.first + filename = arguments.delete_at 1 + class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename) + end + + def structure_load(*arguments) + configuration = arguments.first + filename = arguments.delete_at 1 + class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename) + end + + private + + def class_for_adapter(adapter) + key = @tasks.keys.detect { |pattern| adapter[pattern] } + @tasks[key] + end + + def each_current_configuration(environment) + environments = [environment] + environments << 'test' if environment.development? + + configurations = ActiveRecord::Base.configurations.values_at(*environments) + configurations.compact.each do |configuration| + yield configuration unless configuration['database'].blank? + end + end + + def each_local_configuration + ActiveRecord::Base.configurations.each_value do |configuration| + next unless configuration['database'] + + if local_database?(configuration) + yield configuration + else + $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + end + end + end + + def local_database?(configuration) + configuration['host'].blank? || LOCAL_HOSTS.include?(configuration['host']) + end + end + 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 0000000000..85d08402f9 --- /dev/null +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,118 @@ +module ActiveRecord + module Tasks # :nodoc: + class MySQLDatabaseTasks # :nodoc: + + 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 + + def drop + establish_connection configuration + connection.drop_database configuration['database'] + end + + def purge + establish_connection :test + connection.recreate_database configuration['database'], creation_options + end + + def charset + connection.charset + end + + def collation + connection.collation + end + + def structure_dump(filename) + establish_connection configuration + File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } + end + + def structure_load(filename) + args = ['mysql'] + args.concat(['--user', configuration['username']]) if configuration['username'] + args << "--password=#{configuration['password']}" if configuration['password'] + args.concat(['--default-character-set', configuration['charset']]) if configuration['charset'] + configuration.slice('host', 'port', 'socket', 'database').each do |k, v| + args.concat([ "--#{k}", v ]) if v + end + args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) + Kernel.system(*args) + end + + private + + def configuration + @configuration + end + + 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' + 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 + end +end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb new file mode 100644 index 0000000000..ea5cb888fb --- /dev/null +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -0,0 +1,85 @@ +require 'shellwords' + +module ActiveRecord + module Tasks # :nodoc: + class PostgreSQLDatabaseTasks # :nodoc: + + DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8' + + delegate :connection, :establish_connection, :clear_active_connections!, + to: ActiveRecord::Base + + def initialize(configuration) + @configuration = configuration + end + + def create(master_established = false) + establish_master_connection unless master_established + connection.create_database configuration['database'], + configuration.merge('encoding' => encoding) + establish_connection configuration + end + + def drop + establish_master_connection + connection.drop_database configuration['database'] + end + + def charset + connection.encoding + end + + def collation + connection.collation + end + + def purge + clear_active_connections! + drop + create true + end + + def structure_dump(filename) + set_psql_env + search_path = configuration['schema_search_path'] + unless search_path.blank? + search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ") + end + + command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}" + raise 'Error dumping database' unless Kernel.system(command) + + File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" } + end + + def structure_load(filename) + set_psql_env + Kernel.system("psql -f #{filename} #{configuration['database']}") + end + + private + + def configuration + @configuration + end + + def encoding + configuration['encoding'] || DEFAULT_ENCODING + end + + def establish_master_connection + establish_connection configuration.merge( + 'database' => 'postgres', + 'schema_search_path' => 'public' + ) + end + + def set_psql_env + ENV['PGHOST'] = configuration['host'] if configuration['host'] + ENV['PGPORT'] = configuration['port'].to_s if configuration['port'] + ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password'] + ENV['PGUSER'] = configuration['username'].to_s if configuration['username'] + end + end + 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 0000000000..da01058a82 --- /dev/null +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,55 @@ +module ActiveRecord + module Tasks # :nodoc: + class SQLiteDatabaseTasks # :nodoc: + + delegate :connection, :establish_connection, to: ActiveRecord::Base + + def initialize(configuration, root = Rails.root) + @configuration, @root = configuration, root + end + + def create + if File.exist?(configuration['database']) + $stderr.puts "#{configuration['database']} already exists" + return + end + + establish_connection configuration + connection + end + + def drop + require 'pathname' + path = Pathname.new configuration['database'] + file = path.absolute? ? path.to_s : File.join(root, path) + + FileUtils.rm(file) if File.exist?(file) + end + alias :purge :drop + + def charset + connection.encoding + end + + def structure_dump(filename) + dbfile = configuration['database'] + `sqlite3 #{dbfile} .schema > #{filename}` + end + + def structure_load(filename) + dbfile = configuration['database'] + `sqlite3 #{dbfile} < "#{filename}"` + end + + private + + def configuration + @configuration + end + + def root + @root + end + end + end +end |