schema_dumper_test.rb 12.8 KB
Newer Older
1
require "cases/helper"
2

3

4
class SchemaDumperTest < ActiveRecord::TestCase
5
  def setup
A
Aaron Patterson 已提交
6
    super
7
    ActiveRecord::SchemaMigration.create_table
8 9 10
    @stream = StringIO.new
  end

11
  def standard_dump
12
    @stream = StringIO.new
13
    ActiveRecord::SchemaDumper.ignore_tables = []
14 15 16 17
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, @stream)
    @stream.string
  end

18 19 20
  def test_dump_schema_information_outputs_lexically_ordered_versions
    versions = %w{ 20100101010101 20100201010101 20100301010101 }
    versions.reverse.each do |v|
21
      ActiveRecord::SchemaMigration.create!(:version => v)
22 23 24 25 26 27
    end

    schema_info = ActiveRecord::Base.connection.dump_schema_information
    assert_match(/20100201010101.*20100301010101/m, schema_info)
  end

28 29
  def test_magic_comment
    assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
30
  end
J
Jeremy Kemper 已提交
31

32 33 34 35 36 37
  def test_schema_dump
    output = standard_dump
    assert_match %r{create_table "accounts"}, output
    assert_match %r{create_table "authors"}, output
    assert_no_match %r{create_table "schema_migrations"}, output
  end
J
Jeremy Kemper 已提交
38

39 40 41 42
  def test_schema_dump_excludes_sqlite_sequence
    output = standard_dump
    assert_no_match %r{create_table "sqlite_sequence"}, output
  end
43

44 45 46 47 48
  def test_schema_dump_includes_camelcase_table_name
    output = standard_dump
    assert_match %r{create_table "CamelCase"}, output
  end

49 50 51 52 53 54 55 56
  def assert_line_up(lines, pattern, required = false)
    return assert(true) if lines.empty?
    matches = lines.map { |line| line.match(pattern) }
    assert matches.all? if required
    matches.compact!
    return assert(true) if matches.empty?
    assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length
  end
57

58 59 60
  def column_definition_lines(output = standard_dump)
    output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }
  end
61

62 63 64
  def test_types_line_up
    column_definition_lines.each do |column_set|
      next if column_set.empty?
65

66 67 68
      lengths = column_set.map do |column|
        if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
          match[0].length
69 70
        end
      end
J
Jeremy Kemper 已提交
71

72
      assert_equal 1, lengths.uniq.length
73
    end
74
  end
J
Jeremy Kemper 已提交
75

76 77
  def test_arguments_line_up
    column_definition_lines.each do |column_set|
78 79 80
      assert_line_up(column_set, /default: /)
      assert_line_up(column_set, /limit: /)
      assert_line_up(column_set, /null: /)
81
    end
82
  end
J
Jeremy Kemper 已提交
83

84 85 86 87
  def test_no_dump_errors
    output = standard_dump
    assert_no_match %r{\# Could not dump table}, output
  end
J
Jeremy Kemper 已提交
88

89 90
  def test_schema_dump_includes_not_null_columns
    stream = StringIO.new
91

92 93 94
    ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    output = stream.string
95
    assert_match %r{null: false}, output
96
  end
J
Jeremy Kemper 已提交
97

98 99 100 101 102 103 104 105
  def test_schema_dump_includes_limit_constraint_for_integer_columns
    stream = StringIO.new

    ActiveRecord::SchemaDumper.ignore_tables = [/^(?!integer_limits)/]
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    output = stream.string

    if current_adapter?(:PostgreSQLAdapter)
106 107
      assert_match %r{c_int_1.*limit: 2}, output
      assert_match %r{c_int_2.*limit: 2}, output
108 109 110

      # int 3 is 4 bytes in postgresql
      assert_match %r{c_int_3.*}, output
111
      assert_no_match %r{c_int_3.*limit:}, output
112 113

      assert_match %r{c_int_4.*}, output
114
      assert_no_match %r{c_int_4.*limit:}, output
115
    elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
116 117 118
      assert_match %r{c_int_1.*limit: 1}, output
      assert_match %r{c_int_2.*limit: 2}, output
      assert_match %r{c_int_3.*limit: 3}, output
119 120 121

      assert_match %r{c_int_4.*}, output
      assert_no_match %r{c_int_4.*:limit}, output
122
    elsif current_adapter?(:SQLite3Adapter)
123 124 125 126
      assert_match %r{c_int_1.*limit: 1}, output
      assert_match %r{c_int_2.*limit: 2}, output
      assert_match %r{c_int_3.*limit: 3}, output
      assert_match %r{c_int_4.*limit: 4}, output
127 128
    end
    assert_match %r{c_int_without_limit.*}, output
129
    assert_no_match %r{c_int_without_limit.*limit:}, output
130

131
    if current_adapter?(:SQLite3Adapter)
132 133 134 135
      assert_match %r{c_int_5.*limit: 5}, output
      assert_match %r{c_int_6.*limit: 6}, output
      assert_match %r{c_int_7.*limit: 7}, output
      assert_match %r{c_int_8.*limit: 8}, output
136
    elsif current_adapter?(:OracleAdapter)
137 138 139 140
      assert_match %r{c_int_5.*limit: 5}, output
      assert_match %r{c_int_6.*limit: 6}, output
      assert_match %r{c_int_7.*limit: 7}, output
      assert_match %r{c_int_8.*limit: 8}, output
141
    else
142 143 144 145
      assert_match %r{c_int_5.*limit: 8}, output
      assert_match %r{c_int_6.*limit: 8}, output
      assert_match %r{c_int_7.*limit: 8}, output
      assert_match %r{c_int_8.*limit: 8}, output
146 147 148
    end
  end

149 150 151 152 153 154 155 156 157 158 159 160 161
  def test_schema_dump_with_string_ignored_table
    stream = StringIO.new

    ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    output = stream.string
    assert_no_match %r{create_table "accounts"}, output
    assert_match %r{create_table "authors"}, output
    assert_no_match %r{create_table "schema_migrations"}, output
  end

  def test_schema_dump_with_regexp_ignored_table
    stream = StringIO.new
162

163 164 165 166 167 168 169
    ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    output = stream.string
    assert_no_match %r{create_table "accounts"}, output
    assert_match %r{create_table "authors"}, output
    assert_no_match %r{create_table "schema_migrations"}, output
  end
J
Jeremy Kemper 已提交
170

171 172 173 174
  def test_schema_dump_illegal_ignored_table_value
    stream = StringIO.new
    ActiveRecord::SchemaDumper.ignore_tables = [5]
    assert_raise(StandardError) do
175 176
      ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    end
177
  end
178

179 180
  def test_schema_dumps_index_columns_in_right_order
    index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
181
    assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
182
  end
183

184 185 186
  def test_schema_dumps_partial_indices
    index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
    if current_adapter?(:PostgreSQLAdapter)
187
      assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition
188
    else
189
      assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
190 191 192
    end
  end

193 194 195 196
  def test_schema_dump_should_honor_nonstandard_primary_keys
    output = standard_dump
    match = output.match(%r{create_table "movies"(.*)do})
    assert_not_nil(match, "nonstandardpk table not found")
197
    assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
198
  end
199

200
  if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
201 202
    def test_schema_dump_should_not_add_default_value_for_mysql_text_field
      output = standard_dump
203
      assert_match %r{t.text\s+"body",\s+null: false$}, output
204
    end
205

206 207 208 209 210 211
    def test_schema_dump_includes_length_for_mysql_binary_fields
      output = standard_dump
      assert_match %r{t.binary\s+"var_binary",\s+limit: 255$}, output
      assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output
    end

212 213
    def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
      output = standard_dump
214
      assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output
215
      assert_match %r{t.binary\s+"normal_blob"$}, output
216 217 218
      assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output
      assert_match %r{t.binary\s+"long_blob",\s+limit: 2147483647$}, output
      assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output
219
      assert_match %r{t.text\s+"normal_text"$}, output
220 221
      assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
      assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
222
    end
223
  end
224

225 226 227 228 229
  def test_schema_dump_includes_decimal_options
    stream = StringIO.new
    ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
    output = stream.string
230
    assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
231
  end
232

233
  if current_adapter?(:PostgreSQLAdapter)
234 235 236 237 238
    def test_schema_dump_includes_extensions
      connection = ActiveRecord::Base.connection
      return skip unless connection.supports_extensions?
      unless connection.extension_enabled?('hstore')
        connection.enable_extension 'hstore'
239
      end
240 241
      output = standard_dump
      assert_match %r{enable_extension "hstore"}, output
242 243
    end

244 245 246 247 248 249
    def test_schema_dump_includes_xml_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_xml_data_type"} =~ output
        assert_match %r{t.xml "data"}, output
      end
    end
250

251 252 253
    def test_schema_dump_includes_json_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_json_data_type"} =~ output
254
        assert_match %r|t.json "json_data", default: {}|, output
255 256 257
      end
    end

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    def test_schema_dump_includes_inet_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_network_address"} =~ output
        assert_match %r{t.inet "inet_address"}, output
      end
    end

    def test_schema_dump_includes_cidr_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_network_address"} =~ output
        assert_match %r{t.cidr "cidr_address"}, output
      end
    end

    def test_schema_dump_includes_macaddr_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_network_address"} =~ output
        assert_match %r{t.macaddr "macaddr_address"}, output
      end
    end

279 280 281 282 283 284 285
    def test_schema_dump_includes_uuid_shorthand_definition
      output = standard_dump
      if %r{create_table "poistgresql_uuids"} =~ output
        assert_match %r{t.uuid "guid"}, output
      end
    end

286
    def test_schema_dump_includes_hstores_shorthand_definition
J
Joel 已提交
287 288
      output = standard_dump
      if %r{create_table "postgresql_hstores"} =~ output
289
        assert_match %r[t.hstore "hash_store", default: {}], output
J
Joel 已提交
290 291 292
      end
    end

293 294 295 296 297 298 299
    def test_schema_dump_includes_ltrees_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_ltrees"} =~ output
        assert_match %r[t.ltree "path"], output
      end
    end

300 301 302 303 304 305 306 307
    def test_schema_dump_includes_arrays_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_arrays"} =~ output
        assert_match %r[t.text\s+"nicknames",\s+array: true], output
        assert_match %r[t.integer\s+"commission_by_quarter",\s+array: true], output
      end
    end

308 309 310 311 312 313
    def test_schema_dump_includes_tsvector_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_tsvectors"} =~ output
        assert_match %r{t.tsvector "text_vector"}, output
      end
    end
314 315
  end

316 317
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
    output = standard_dump
318 319
    # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
    if current_adapter?(:OracleAdapter)
320
      assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38,\s+scale: 0}, output
321
    else
322
      assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55,\s+scale: 0}, output
323
    end
324
  end
325 326 327 328 329

  def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added
    output = standard_dump
    match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
    assert_not_nil(match, "goofy_string_id table not found")
330 331
    assert_match %r(id: false), match[1], "no table id not preserved"
    assert_match %r{t.string[[:space:]]+"id",[[:space:]]+null: false$}, match[2], "non-primary key id column not preserved"
332
  end
333 334 335

  def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
    output = standard_dump
336
    assert_match %r{create_table "subscribers", id: false}, output
337
  end
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369

  class CreateDogMigration < ActiveRecord::Migration
    def up
      create_table("dogs") do |t|
        t.column :name, :string
      end
      add_index "dogs", [:name]
    end
    def down
      drop_table("dogs")
    end
  end

  def test_schema_dump_with_table_name_prefix_and_suffix
    original, $stdout = $stdout, StringIO.new
    ActiveRecord::Base.table_name_prefix = 'foo_'
    ActiveRecord::Base.table_name_suffix = '_bar'

    migration = CreateDogMigration.new
    migration.migrate(:up)

    output = standard_dump
    assert_no_match %r{create_table "foo_.+_bar"}, output
    assert_no_match %r{create_index "foo_.+_bar"}, output
    assert_no_match %r{create_table "schema_migrations"}, output
  ensure
    migration.migrate(:down)

    ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ''
    $stdout = original
  end

370
end