schema_dumper_test.rb 14.2 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 206 207 208 209
  def test_schema_dump_should_use_false_as_default
    output = standard_dump
    assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
  end

210
  if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
211 212
    def test_schema_dump_should_not_add_default_value_for_mysql_text_field
      output = standard_dump
213
      assert_match %r{t.text\s+"body",\s+null: false$}, output
214
    end
215

216 217 218 219 220 221
    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

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

    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
239
  end
240

241 242 243 244 245
  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
246
    assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
247
  end
248

249
  if current_adapter?(:PostgreSQLAdapter)
250 251 252 253 254
    def test_schema_dump_includes_bigint_default
      output = standard_dump
      assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
    end

255 256
    def test_schema_dump_includes_extensions
      connection = ActiveRecord::Base.connection
257
      skip unless connection.supports_extensions?
258

259
      connection.stubs(:extensions).returns(['hstore'])
260
      output = standard_dump
261
      assert_match "# These are extensions that must be enabled", output
262
      assert_match %r{enable_extension "hstore"}, output
263

264
      connection.stubs(:extensions).returns([])
265 266 267
      output = standard_dump
      assert_no_match "# These are extensions that must be enabled", output
      assert_no_match %r{enable_extension}, output
268 269
    end

270 271 272 273 274 275
    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
276

277 278 279
    def test_schema_dump_includes_json_shorthand_definition
      output = standard_dump
      if %r{create_table "postgresql_json_data_type"} =~ output
280
        assert_match %r|t.json "json_data", default: {}|, output
281 282 283
      end
    end

284 285
    def test_schema_dump_includes_inet_shorthand_definition
      output = standard_dump
286 287
      if %r{create_table "postgresql_network_addresses"} =~ output
        assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
288 289 290 291 292
      end
    end

    def test_schema_dump_includes_cidr_shorthand_definition
      output = standard_dump
293 294
      if %r{create_table "postgresql_network_addresses"} =~ output
        assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
295 296 297 298 299
      end
    end

    def test_schema_dump_includes_macaddr_shorthand_definition
      output = standard_dump
300 301
      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
302 303 304
      end
    end

305 306
    def test_schema_dump_includes_uuid_shorthand_definition
      output = standard_dump
307
      if %r{create_table "postgresql_uuids"} =~ output
308 309 310 311
        assert_match %r{t.uuid "guid"}, output
      end
    end

312
    def test_schema_dump_includes_hstores_shorthand_definition
J
Joel 已提交
313 314
      output = standard_dump
      if %r{create_table "postgresql_hstores"} =~ output
315
        assert_match %r[t.hstore "hash_store", default: {}], output
J
Joel 已提交
316 317 318
      end
    end

319 320 321 322 323 324 325
    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

326 327 328 329 330 331 332 333
    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

334 335 336 337 338 339
    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
340 341
  end

342 343
  def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
    output = standard_dump
344 345
    # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
    if current_adapter?(:OracleAdapter)
346
      assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38,\s+scale: 0}, output
347
    else
348
      assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55,\s+scale: 0}, output
349
    end
350
  end
351 352 353 354 355

  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")
356 357
    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"
358
  end
359 360 361

  def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
    output = standard_dump
362
    assert_match %r{create_table "subscribers", id: false}, output
363
  end
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

  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

396
end