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

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

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

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

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

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

31 32 33 34 35 36
  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 已提交
37

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

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

48 49 50 51 52 53 54 55
  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
56

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

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

65 66 67
      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
68 69
        end
      end
J
Jeremy Kemper 已提交
70

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

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

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

88 89
  def test_schema_dump_includes_not_null_columns
    stream = StringIO.new
90

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

97 98 99 100 101 102 103 104
  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)
105 106
      assert_match %r{c_int_1.*limit: 2}, output
      assert_match %r{c_int_2.*limit: 2}, output
107 108 109

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

      assert_match %r{c_int_4.*}, output
113
      assert_no_match %r{c_int_4.*limit:}, output
114
    elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
115 116 117
      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
118 119 120

      assert_match %r{c_int_4.*}, output
      assert_no_match %r{c_int_4.*:limit}, output
121
    elsif current_adapter?(:SQLite3Adapter)
122 123 124 125
      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
126 127
    end
    assert_match %r{c_int_without_limit.*}, output
128
    assert_no_match %r{c_int_without_limit.*limit:}, output
129

130
    if current_adapter?(:SQLite3Adapter)
131 132 133 134
      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
135
    elsif current_adapter?(:OracleAdapter)
136 137 138 139
      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
140
    else
141 142 143 144
      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
145 146 147
    end
  end

148 149 150 151 152 153 154 155 156 157 158 159 160
  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
161

162 163 164 165 166 167 168
    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 已提交
169

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

178 179
  def test_schema_dumps_index_columns_in_right_order
    index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
180 181 182 183
    if current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter)
      assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
    else
      assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
184
    end
185
  end
186

187 188 189
  def test_schema_dumps_partial_indices
    index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
    if current_adapter?(:PostgreSQLAdapter)
190
      assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
191
    elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
192
      assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
193
    else
194
      assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
195 196 197
    end
  end

198 199 200 201
  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")
202
    assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
203
  end
204

205
  if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
206 207
    def test_schema_dump_should_not_add_default_value_for_mysql_text_field
      output = standard_dump
208
      assert_match %r{t.text\s+"body",\s+null: false$}, output
209
    end
210

211 212 213 214 215 216
    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

217 218
    def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
      output = standard_dump
219
      assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output
220
      assert_match %r{t.binary\s+"normal_blob"$}, output
221 222 223
      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
224
      assert_match %r{t.text\s+"normal_text"$}, output
225 226
      assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
      assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
227
    end
228 229 230 231 232 233

    def test_schema_dumps_index_type
      output = standard_dump
      assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
      assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
    end
234
  end
235

236 237 238 239 240
  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
241
    assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
242
  end
243

244
  if current_adapter?(:PostgreSQLAdapter)
245 246
    def test_schema_dump_includes_extensions
      connection = ActiveRecord::Base.connection
247
      skip unless connection.supports_extensions?
248

249
      connection.stubs(:extensions).returns(['hstore'])
250
      output = standard_dump
251
      assert_match "# These are extensions that must be enabled", output
252
      assert_match %r{enable_extension "hstore"}, output
253

254
      connection.stubs(:extensions).returns([])
255 256 257
      output = standard_dump
      assert_no_match "# These are extensions that must be enabled", output
      assert_no_match %r{enable_extension}, output
258 259
    end

260 261 262 263 264 265
    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
266

267 268 269
    def test_schema_dump_includes_json_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_json_data_type"} =~ output
270
        assert_match %r|t.json "json_data", default: {}|, output
271 272 273
      end
    end

274 275
    def test_schema_dump_includes_inet_shorthand_definition
      output = standard_dump
276 277
      if %r{create_table "postgresql_network_addresses"} =~ output
        assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
278 279 280 281 282
      end
    end

    def test_schema_dump_includes_cidr_shorthand_definition
      output = standard_dump
283 284
      if %r{create_table "postgresql_network_addresses"} =~ output
        assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
285 286 287 288 289
      end
    end

    def test_schema_dump_includes_macaddr_shorthand_definition
      output = standard_dump
290 291
      if %r{create_table "postgresql_network_addresses"} =~ output
        assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
292 293 294
      end
    end

295 296 297 298 299 300 301
    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

302
    def test_schema_dump_includes_hstores_shorthand_definition
J
Joel 已提交
303 304
      output = standard_dump
      if %r{create_table "postgresql_hstores"} =~ output
305
        assert_match %r[t.hstore "hash_store", default: {}], output
J
Joel 已提交
306 307 308
      end
    end

309 310 311 312 313 314 315
    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

316 317 318 319 320 321 322 323
    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

324 325 326 327 328 329
    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
330 331
  end

332 333
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
    output = standard_dump
334 335
    # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
    if current_adapter?(:OracleAdapter)
336
      assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38,\s+scale: 0}, output
337
    else
338
      assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55,\s+scale: 0}, output
339
    end
340
  end
341 342 343 344 345

  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")
346 347
    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"
348
  end
349 350 351

  def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
    output = standard_dump
352
    assert_match %r{create_table "subscribers", id: false}, output
353
  end
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

  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

386
end