dump 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.autotest +13 -0
  2. data/.gitignore +12 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.markdown +250 -0
  5. data/dump.gemspec +22 -0
  6. data/lib/dump.rb +3 -0
  7. data/lib/dump/capistrano.rb +1 -0
  8. data/lib/dump/railtie.rb +8 -0
  9. data/lib/dump_rake.rb +85 -0
  10. data/lib/dump_rake/archive_tar_minitar_fix.rb +8 -0
  11. data/lib/dump_rake/assets.rb +22 -0
  12. data/lib/dump_rake/continious_timeout.rb +38 -0
  13. data/lib/dump_rake/dump.rb +175 -0
  14. data/lib/dump_rake/dump_reader.rb +289 -0
  15. data/lib/dump_rake/dump_writer.rb +119 -0
  16. data/lib/dump_rake/env.rb +139 -0
  17. data/lib/dump_rake/env/filter.rb +26 -0
  18. data/lib/dump_rake/rails_root.rb +12 -0
  19. data/lib/dump_rake/table_manipulation.rb +131 -0
  20. data/lib/generators/assets_config/assets_config_generator.rb +16 -0
  21. data/lib/generators/assets_config/templates/assets +8 -0
  22. data/lib/tasks/assets.rake +17 -0
  23. data/lib/tasks/dump.rake +27 -0
  24. data/recipes/dump.rb +343 -0
  25. data/script/update_readme +21 -0
  26. data/spec/.gitignore +1 -0
  27. data/spec/.tmignore +1 -0
  28. data/spec/cycle_spec.rb +229 -0
  29. data/spec/db/database.example.yml +19 -0
  30. data/spec/db/schema.rb +7 -0
  31. data/spec/dummy-3.1.3/.gitignore +15 -0
  32. data/spec/dummy-3.1.3/.rspec +1 -0
  33. data/spec/dummy-3.1.3/Gemfile +23 -0
  34. data/spec/dummy-3.1.3/Gemfile.lock +159 -0
  35. data/spec/dummy-3.1.3/README +261 -0
  36. data/spec/dummy-3.1.3/Rakefile +7 -0
  37. data/spec/dummy-3.1.3/app/assets/images/rails.png +0 -0
  38. data/spec/dummy-3.1.3/app/assets/javascripts/application.js +9 -0
  39. data/spec/dummy-3.1.3/app/assets/stylesheets/application.css +7 -0
  40. data/spec/dummy-3.1.3/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy-3.1.3/app/helpers/application_helper.rb +2 -0
  42. data/spec/dummy-3.1.3/app/mailers/.gitkeep +0 -0
  43. data/spec/dummy-3.1.3/app/models/.gitkeep +0 -0
  44. data/spec/dummy-3.1.3/app/views/layouts/application.html.erb +14 -0
  45. data/spec/dummy-3.1.3/config.ru +4 -0
  46. data/spec/dummy-3.1.3/config/application.rb +54 -0
  47. data/spec/dummy-3.1.3/config/boot.rb +6 -0
  48. data/spec/dummy-3.1.3/config/database.yml +25 -0
  49. data/spec/dummy-3.1.3/config/environment.rb +5 -0
  50. data/spec/dummy-3.1.3/config/environments/development.rb +30 -0
  51. data/spec/dummy-3.1.3/config/environments/production.rb +60 -0
  52. data/spec/dummy-3.1.3/config/environments/test.rb +39 -0
  53. data/spec/dummy-3.1.3/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/dummy-3.1.3/config/initializers/inflections.rb +10 -0
  55. data/spec/dummy-3.1.3/config/initializers/mime_types.rb +5 -0
  56. data/spec/dummy-3.1.3/config/initializers/secret_token.rb +7 -0
  57. data/spec/dummy-3.1.3/config/initializers/session_store.rb +8 -0
  58. data/spec/dummy-3.1.3/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/dummy-3.1.3/config/locales/en.yml +5 -0
  60. data/spec/dummy-3.1.3/config/routes.rb +58 -0
  61. data/spec/dummy-3.1.3/db/seeds.rb +7 -0
  62. data/spec/dummy-3.1.3/doc/README_FOR_APP +2 -0
  63. data/spec/dummy-3.1.3/lib/assets/.gitkeep +0 -0
  64. data/spec/dummy-3.1.3/lib/tasks/.gitkeep +0 -0
  65. data/spec/dummy-3.1.3/log/.gitkeep +0 -0
  66. data/spec/dummy-3.1.3/public/404.html +26 -0
  67. data/spec/dummy-3.1.3/public/422.html +26 -0
  68. data/spec/dummy-3.1.3/public/500.html +26 -0
  69. data/spec/dummy-3.1.3/public/favicon.ico +0 -0
  70. data/spec/dummy-3.1.3/public/index.html +241 -0
  71. data/spec/dummy-3.1.3/public/robots.txt +5 -0
  72. data/spec/dummy-3.1.3/script/rails +6 -0
  73. data/spec/dummy-3.1.3/spec/spec_helper.rb +32 -0
  74. data/spec/dummy-3.1.3/vendor/assets/stylesheets/.gitkeep +0 -0
  75. data/spec/dummy-3.1.3/vendor/plugins/.gitkeep +0 -0
  76. data/spec/lib/dump_rake/dump_reader_spec.rb +638 -0
  77. data/spec/lib/dump_rake/dump_spec.rb +291 -0
  78. data/spec/lib/dump_rake/dump_writer_spec.rb +328 -0
  79. data/spec/lib/dump_rake/env/filter_spec.rb +56 -0
  80. data/spec/lib/dump_rake/env_spec.rb +139 -0
  81. data/spec/lib/dump_rake/rails_root_spec.rb +45 -0
  82. data/spec/lib/dump_rake/table_manipulation_spec.rb +256 -0
  83. data/spec/lib/dump_rake_spec.rb +326 -0
  84. data/spec/recipes/dump_spec.rb +553 -0
  85. data/spec/spec.opts +4 -0
  86. data/spec/spec_helper.rb +34 -0
  87. data/spec/tasks/assets_spec.rb +92 -0
  88. data/spec/tasks/dump_spec.rb +107 -0
  89. metadata +272 -0
@@ -0,0 +1,119 @@
1
+ class DumpRake
2
+ class DumpWriter < Dump
3
+ attr_reader :stream, :config
4
+
5
+ def self.create(path)
6
+ new(path).open do |dump|
7
+ ActiveRecord::Base.logger.silence do
8
+ dump.write_schema
9
+
10
+ dump.write_tables
11
+ dump.write_assets
12
+
13
+ dump.write_config
14
+ end
15
+ end
16
+ end
17
+
18
+ def open
19
+ Pathname.new(path).dirname.mkpath
20
+ Zlib::GzipWriter.open(path) do |gzip|
21
+ gzip.mtime = Time.utc(2000)
22
+ lock do
23
+ Archive::Tar::Minitar.open(gzip, 'w') do |stream|
24
+ @stream = stream
25
+ @config = {:tables => {}}
26
+ yield(self)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def create_file(name)
33
+ Tempfile.open('dump') do |temp|
34
+ yield(temp)
35
+ temp.open
36
+ stream.tar.add_file_simple(name, :mode => 0100444, :size => temp.length) do |f|
37
+ f.write(temp.read(4096)) until temp.eof?
38
+ end
39
+ end
40
+ end
41
+
42
+ def write_schema
43
+ create_file('schema.rb') do |f|
44
+ DumpRake::Env.with_env('SCHEMA' => f.path) do
45
+ Rake::Task['db:schema:dump'].invoke
46
+ end
47
+ end
48
+ end
49
+
50
+ def write_tables
51
+ verify_connection
52
+ tables_to_dump.with_progress('Tables') do |table|
53
+ write_table(table)
54
+ end
55
+ end
56
+
57
+ def write_table(table)
58
+ row_count = table_row_count(table)
59
+ config[:tables][table] = row_count
60
+ Progress.start(table, 1 + row_count) do
61
+ create_file("#{table}.dump") do |f|
62
+ columns = table_columns(table)
63
+ column_names = columns.map(&:name).sort
64
+ columns_by_name = columns.index_by(&:name)
65
+
66
+ Marshal.dump(column_names, f)
67
+ Progress.step
68
+
69
+ each_table_row(table, row_count) do |row|
70
+ values = column_names.map do |column|
71
+ columns_by_name[column].type_cast(row[column])
72
+ end
73
+ Marshal.dump(values, f)
74
+ Progress.step
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def write_assets
81
+ assets = assets_to_dump
82
+ if assets.present?
83
+ config[:assets] = {}
84
+ Dir.chdir(DumpRake::RailsRoot) do
85
+ assets = Dir[*assets].uniq
86
+ assets.with_progress('Assets') do |asset|
87
+ paths = Dir[File.join(asset, '**/*')]
88
+ files = paths.select{ |path| File.file?(path) }
89
+ config[:assets][asset] = {:total => paths.length, :files => files.length}
90
+ assets_root_link do |tmpdir, prefix|
91
+ paths.with_progress(asset) do |entry|
92
+ begin
93
+ Archive::Tar::Minitar.pack_file(File.join(prefix, entry), stream)
94
+ rescue => e
95
+ $stderr.puts "Skipped asset due to error #{e}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def write_config
105
+ create_file('config') do |f|
106
+ Marshal.dump(config, f)
107
+ end
108
+ end
109
+
110
+ def assets_to_dump
111
+ begin
112
+ Rake::Task['assets'].invoke
113
+ DumpRake::Env[:assets].split(DumpRake::Assets::SPLITTER)
114
+ rescue
115
+ []
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,139 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'dump_rake/env/filter'
4
+
5
+ class DumpRake
6
+ module Env
7
+ DICTIONARY = {
8
+ :desc => %w[DESC DESCRIPTION],
9
+ :like => %w[LIKE VER VERSION],
10
+ :tags => %w[TAGS TAG],
11
+ :leave => %w[LEAVE],
12
+ :summary => %w[SUMMARY],
13
+ :assets => %w[ASSETS],
14
+ :tables => %w[TABLES],
15
+ :backup => %w[BACKUP AUTOBACKUP AUTO_BACKUP],
16
+ :transfer_via => %w[TRANSFER_VIA],
17
+ :migrate_down => %w[MIGRATE_DOWN],
18
+ :restore_schema => %w[RESTORE_SCHEMA],
19
+ :restore_tables => %w[RESTORE_TABLES],
20
+ :restore_assets => %w[RESTORE_ASSETS],
21
+ :show_size => %w[SHOW_SIZE], # internal
22
+ }.freeze unless defined? DICTIONARY
23
+
24
+ EXPLANATIONS = {
25
+ :desc => 'free form description of dump',
26
+ :like => 'filter dumps by full dump name',
27
+ :tags => 'comma separated list of tags',
28
+ :leave => 'number of dumps to leave',
29
+ :summary => 'output info about dump: "1", "true" or "yes" for basic info, "2" or "schema" to display schema as well',
30
+ :assets => 'comma or colon separated list of paths or globs to dump',
31
+ :tables => 'comma separated list of tables to dump or if prefixed by "-" — to skip; by default only sessions table is skipped; schema_info and schema_migrations are always included if they are present',
32
+ :backup => 'no autobackup if you pass "0", "no" or "false"',
33
+ :transfer_via => 'transfer method (rsync, sftp or scp)',
34
+ :migrate_down => 'don\'t run down for migrations not present in dump if you pass "0", "no" or "false"; pass "reset" to recreate (drop and create) db',
35
+ :restore_schema => 'don\'t read/change schema if you pass "0", "no" or "false" (useful to just restore data for table; note that schema info tables are also not restored)',
36
+ :restore_tables => 'works as TABLES, but for restoring',
37
+ :restore_assets => 'works as ASSETS, but for restoring',
38
+ }.freeze unless defined? EXPLANATIONS
39
+
40
+ class << self
41
+ def with_env(hash)
42
+ old = {}
43
+ hash.each do |key, value|
44
+ key = DICTIONARY[key].first if DICTIONARY[key]
45
+ old[key] = ENV[key]
46
+ ENV[key] = value
47
+ end
48
+ begin
49
+ yield
50
+ ensure
51
+ old.each do |key, value|
52
+ ENV[key] = value
53
+ end
54
+ end
55
+ end
56
+
57
+ def with_clean_env(hash = {}, &block)
58
+ empty_env = {}
59
+ DICTIONARY.keys.each{ |key| empty_env[key] = nil }
60
+ with_env(empty_env.merge(hash), &block)
61
+ end
62
+
63
+ def [](key)
64
+ if DICTIONARY[key]
65
+ ENV.values_at(*DICTIONARY[key]).compact.first
66
+ else
67
+ ENV[key]
68
+ end
69
+ end
70
+
71
+ def filter(key, splitter = nil)
72
+ @filters ||= Hash.new{ |hash, key| hash[key] = Filter.new(*key) }
73
+ @filters[[self[key], splitter]]
74
+ end
75
+
76
+ def yes?(key)
77
+ %w[1 y t].include?(first_char(key))
78
+ end
79
+
80
+ def no?(key)
81
+ %w[0 n f].include?(first_char(key))
82
+ end
83
+
84
+ def downcase(key)
85
+ self[key].to_s.downcase.strip
86
+ end
87
+
88
+ def variable_names_for_command(command)
89
+ m = {
90
+ :select => [:like, :tags],
91
+ :assets => [:assets],
92
+ :restore_options => [:migrate_down, :restore_schema, :restore_tables, :restore_assets],
93
+ :transfer_options => [:transfer_via]
94
+ }
95
+
96
+ m[:versions] = m[:select] | [:summary]
97
+ m[:create] = [:desc, :tags, :tables] | m[:assets]
98
+ m[:restore] = m[:select] | m[:restore_options]
99
+ m[:cleanup] = m[:select] | [:leave]
100
+
101
+ m[:transfer] = m[:select] | m[:transfer_options]
102
+
103
+ m[:mirror] = [:backup] | m[:create] | m[:transfer_options] | m[:restore_options]
104
+ m[:backup] = m[:create] | [:transfer_via]
105
+ m[:backup_restore] = m[:transfer] | m[:restore_options]
106
+
107
+ m[command] || []
108
+ end
109
+
110
+ def for_command(command, strings = false)
111
+ variables = variable_names_for_command(command)
112
+ variables.inject({}) do |env, variable|
113
+ value = self[variable]
114
+ env[strings ? DICTIONARY[variable].first : variable] = value if value
115
+ env
116
+ end
117
+ end
118
+
119
+ def stringify!(hash)
120
+ hash.keys.each do |key|
121
+ hash[DICTIONARY[key] ? DICTIONARY[key].first : key.to_s] = hash.delete(key)
122
+ end
123
+ end
124
+
125
+ def explain_variables_for_command(command)
126
+ ".\n" <<
127
+ variable_names_for_command(command).map do |variable_name|
128
+ " #{DICTIONARY[variable_name].join(', ')} — #{EXPLANATIONS[variable_name]}\n"
129
+ end.join('')
130
+ end
131
+
132
+ private
133
+
134
+ def first_char(key)
135
+ downcase(key)[0, 1]
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ class DumpRake
4
+ module Env
5
+ class Filter
6
+ attr_reader :invert, :values, :transparent
7
+ def initialize(s, splitter = nil)
8
+ if s
9
+ s = s.dup
10
+ @invert = !!s.sub!(/^-/, '')
11
+ @values = s.split(splitter || ',').map(&:strip).map(&:downcase).uniq.select(&:present?)
12
+ else
13
+ @transparent = true
14
+ end
15
+ end
16
+
17
+ def pass?(value)
18
+ transparent || (invert ^ values.include?(value.to_s.downcase))
19
+ end
20
+
21
+ def custom_pass?(&block)
22
+ transparent || (invert ^ values.any?(&block))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ class DumpRake
4
+ RailsRoot = case
5
+ when defined?(Rails)
6
+ Rails.root
7
+ when defined?(RAILS_ROOT)
8
+ RAILS_ROOT
9
+ else
10
+ Dir.pwd
11
+ end.to_s
12
+ end
@@ -0,0 +1,131 @@
1
+ class DumpRake
2
+ module TableManipulation
3
+ protected
4
+
5
+ def schema_tables
6
+ %w[schema_info schema_migrations]
7
+ end
8
+
9
+ def verify_connection
10
+ ActiveRecord::Base.connection.verify!(0)
11
+ end
12
+
13
+
14
+ def quote_table_name(table)
15
+ ActiveRecord::Base.connection.quote_table_name(table)
16
+ end
17
+
18
+ def quote_column_name(column)
19
+ ActiveRecord::Base.connection.quote_column_name(column)
20
+ end
21
+
22
+ def quote_value(value)
23
+ ActiveRecord::Base.connection.quote(value)
24
+ end
25
+
26
+
27
+ def clear_table(table_sql)
28
+ ActiveRecord::Base.connection.delete("DELETE FROM #{table_sql}", 'Clearing table')
29
+ end
30
+
31
+ def insert_into_table(table_sql, columns_sql, values_sql)
32
+ values_sql = values_sql.join(',') if values_sql.is_a?(Array)
33
+ ActiveRecord::Base.connection.insert("INSERT INTO #{table_sql} #{columns_sql} VALUES #{values_sql}", 'Loading dump')
34
+ end
35
+
36
+ def fix_sequence!(table)
37
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
38
+ ActiveRecord::Base.connection.reset_pk_sequence!(table)
39
+ end
40
+ end
41
+
42
+ def join_for_sql(quoted)
43
+ "(#{quoted.join(',')})"
44
+ end
45
+
46
+ def columns_insert_sql(columns)
47
+ join_for_sql(columns.map{ |column| quote_column_name(column) })
48
+ end
49
+
50
+ def values_insert_sql(values)
51
+ join_for_sql(values.map{ |value| quote_value(value) })
52
+ end
53
+
54
+
55
+ def avaliable_tables
56
+ ActiveRecord::Base.connection.tables
57
+ end
58
+
59
+ def tables_to_dump
60
+ if DumpRake::Env[:tables]
61
+ avaliable_tables.select do |table|
62
+ schema_tables.include?(table) || DumpRake::Env.filter(:tables).pass?(table)
63
+ end
64
+ else
65
+ avaliable_tables - %w[sessions]
66
+ end
67
+ end
68
+
69
+ def table_row_count(table)
70
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM #{quote_table_name(table)}").to_i
71
+ end
72
+
73
+ CHUNK_SIZE_MIN = 100 unless const_defined?(:CHUNK_SIZE_MIN)
74
+ CHUNK_SIZE_MAX = 3_000 unless const_defined?(:CHUNK_SIZE_MAX)
75
+ def table_chunk_size(table)
76
+ expected_row_size = table_columns(table).map do |column|
77
+ case column.type
78
+ when :text
79
+ Math.sqrt(column.limit || 2_147_483_647)
80
+ when :string
81
+ Math.sqrt(column.limit || 255)
82
+ else
83
+ column.limit || 10
84
+ end
85
+ end.sum
86
+ [[(10_000_000 / expected_row_size).round, CHUNK_SIZE_MIN].max, CHUNK_SIZE_MAX].min
87
+ end
88
+
89
+ def table_columns(table)
90
+ ActiveRecord::Base.connection.columns(table)
91
+ end
92
+
93
+ def table_has_primary_column?(table)
94
+ # bad test for primary column, but primary even for primary column is nil
95
+ table_columns(table).any?{ |column| column.name == table_primary_key(table) && column.type == :integer }
96
+ end
97
+
98
+ def table_primary_key(table)
99
+ 'id'
100
+ end
101
+
102
+ def each_table_row(table, row_count, &block)
103
+ if table_has_primary_column?(table) && row_count > (chunk_size = table_chunk_size(table))
104
+ # adapted from ActiveRecord::Batches
105
+ primary_key = table_primary_key(table)
106
+ quoted_primary_key = "#{quote_table_name(table)}.#{quote_column_name(primary_key)}"
107
+ select_where_primary_key =
108
+ "SELECT * FROM #{quote_table_name(table)}" +
109
+ " WHERE #{quoted_primary_key} %s" +
110
+ " ORDER BY #{quoted_primary_key} ASC" +
111
+ " LIMIT #{chunk_size}"
112
+ rows = select_all_by_sql(select_where_primary_key % '>= 0')
113
+ until rows.blank?
114
+ rows.each(&block)
115
+ break if rows.length < chunk_size
116
+ rows = select_all_by_sql(select_where_primary_key % "> #{rows.last[primary_key].to_i}")
117
+ end
118
+ else
119
+ table_rows(table).each(&block)
120
+ end
121
+ end
122
+
123
+ def table_rows(table)
124
+ select_all_by_sql("SELECT * FROM #{quote_table_name(table)}")
125
+ end
126
+
127
+ def select_all_by_sql(sql)
128
+ ActiveRecord::Base.connection.select_all(sql)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,16 @@
1
+ case
2
+ when defined?(Rails::Generator::Base)
3
+ class AssetsConfigGenerator < Rails::Generator::Base
4
+ def manifest
5
+ record do |m|
6
+ m.file 'assets', 'config/assets'
7
+ end
8
+ end
9
+ end
10
+ when defined?(Rails::Generators::Base)
11
+ class AssetsConfigGenerator < Rails::Generators::Base
12
+ def create_assets_config
13
+ create_file 'config/assets', File.read(File.join(File.dirname(__FILE__), 'templates/assets'))
14
+ end
15
+ end
16
+ end