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

3
class SchemaTest < ActiveRecord::TestCase
4 5
  self.use_transactional_fixtures = false

6
  SCHEMA_NAME = 'test_schema'
7
  SCHEMA2_NAME = 'test_schema2'
8
  TABLE_NAME = 'things'
9
  CAPITALIZED_TABLE_NAME = 'Things'
10 11
  INDEX_A_NAME = 'a_index_things_on_name'
  INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
12
  INDEX_C_NAME = 'c_index_full_text_search'
13
  INDEX_D_NAME = 'd_index_things_on_description_desc'
14 15 16
  INDEX_A_COLUMN = 'name'
  INDEX_B_COLUMN_S1 = 'email'
  INDEX_B_COLUMN_S2 = 'moment'
17
  INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
18
  INDEX_D_COLUMN = 'description'
19 20 21
  COLUMNS = [
    'id integer',
    'name character varying(50)',
22
    'email character varying(50)',
23
    'description character varying(100)',
24 25
    'moment timestamp without time zone default now()'
  ]
26
  PK_TABLE_NAME = 'table_with_pk'
27 28
  UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq'
  UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk'
29

30
  class Thing1 < ActiveRecord::Base
31
    self.table_name = "test_schema.things"
32 33 34
  end

  class Thing2 < ActiveRecord::Base
35
    self.table_name = "test_schema2.things"
36 37 38
  end

  class Thing3 < ActiveRecord::Base
39
    self.table_name = 'test_schema."things.table"'
40 41
  end

42
  class Thing4 < ActiveRecord::Base
43
    self.table_name = 'test_schema."Things"'
44 45
  end

46
  class Thing5 < ActiveRecord::Base
47
    self.table_name = 'things'
48 49
  end

50 51 52
  def setup
    @connection = ActiveRecord::Base.connection
    @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
53
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})"
54
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{CAPITALIZED_TABLE_NAME}\" (#{COLUMNS.join(',')})"
55 56 57 58 59
    @connection.execute "CREATE SCHEMA #{SCHEMA2_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
    @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_A_COLUMN});"
    @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_A_COLUMN});"
    @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_B_COLUMN_S1});"
    @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_B_COLUMN_S2});"
60 61
    @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME}  USING gin (#{INDEX_C_COLUMN});"
    @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME}  USING gin (#{INDEX_C_COLUMN});"
62 63
    @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_D_COLUMN} DESC);"
    @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME}  USING btree (#{INDEX_D_COLUMN} DESC);"
64
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
65 66
    @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
67 68 69
  end

  def teardown
70
    @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE"
71
    @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
72 73
  end

74 75 76 77
  def test_schema_names
    assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
  end

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
  def test_create_schema
    begin
      @connection.create_schema "test_schema3"
      assert @connection.schema_names.include? "test_schema3"
    ensure
      @connection.drop_schema "test_schema3"
    end
  end

  def test_raise_create_schema_with_existing_schema
    begin
      @connection.create_schema "test_schema3"
      assert_raises(ActiveRecord::StatementInvalid) do
        @connection.create_schema "test_schema3"
      end
    ensure
      @connection.drop_schema "test_schema3"
    end
  end

  def test_drop_schema
    begin
      @connection.create_schema "test_schema3" 
    ensure
      @connection.drop_schema "test_schema3"
    end
    assert !@connection.schema_names.include?("test_schema3")
  end

  def test_raise_drop_schema_with_nonexisting_schema
    assert_raises(ActiveRecord::StatementInvalid) do
      @connection.drop_schema "test_schema3"
    end
  end

113
  def test_schema_change_with_prepared_stmt
J
Jon Leighton 已提交
114
    altered = false
115 116
    @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
    @connection.exec_query "alter table developers add column zomg int", 'sql', []
J
Jon Leighton 已提交
117
    altered = true
118 119
    @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
  ensure
J
Jon Leighton 已提交
120 121 122
    # We are not using DROP COLUMN IF EXISTS because that syntax is only
    # supported by pg 9.X
    @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
123 124
  end

125 126 127 128 129 130 131
  def test_table_exists?
    [Thing1, Thing2, Thing3, Thing4].each do |klass|
      name = klass.table_name
      assert @connection.table_exists?(name), "'#{name}' table should exist"
    end
  end

132 133 134 135 136 137 138 139 140 141 142 143
  def test_table_exists_when_on_schema_search_path
    with_schema_search_path(SCHEMA_NAME) do
      assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found")
    end
  end

  def test_table_exists_when_not_on_schema_search_path
    with_schema_search_path('PUBLIC') do
      assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found")
    end
  end

144 145 146 147
  def test_table_exists_wrong_schema
    assert(!@connection.table_exists?("foo.things"), "table should not exist")
  end

148 149 150 151 152 153 154 155
  def test_table_exists_quoted_names
    [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given|
      assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
    end
    with_schema_search_path(SCHEMA_NAME) do
      given = %("#{TABLE_NAME}")
      assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
    end
156 157
  end

158 159 160 161 162 163
  def test_table_exists_quoted_table
    with_schema_search_path(SCHEMA_NAME) do
        assert(@connection.table_exists?('"things.table"'), "table should exist")
    end
  end

164 165 166 167 168 169
  def test_with_schema_prefixed_table_name
    assert_nothing_raised do
      assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}")
    end
  end

170 171 172 173 174 175
  def test_with_schema_prefixed_capitalized_table_name
    assert_nothing_raised do
      assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{CAPITALIZED_TABLE_NAME}")
    end
  end

176 177 178 179 180 181 182 183
  def test_with_schema_search_path
    assert_nothing_raised do
      with_schema_search_path(SCHEMA_NAME) do
        assert_equal COLUMNS, columns(TABLE_NAME)
      end
    end
  end

184 185 186 187 188 189 190 191 192 193 194 195 196
  def test_proper_encoding_of_table_name
    assert_equal '"table_name"', @connection.quote_table_name('table_name')
    assert_equal '"table.name"', @connection.quote_table_name('"table.name"')
    assert_equal '"schema_name"."table_name"', @connection.quote_table_name('schema_name.table_name')
    assert_equal '"schema_name"."table.name"', @connection.quote_table_name('schema_name."table.name"')
    assert_equal '"schema.name"."table_name"', @connection.quote_table_name('"schema.name".table_name')
    assert_equal '"schema.name"."table.name"', @connection.quote_table_name('"schema.name"."table.name"')
  end

  def test_classes_with_qualified_schema_name
    assert_equal 0, Thing1.count
    assert_equal 0, Thing2.count
    assert_equal 0, Thing3.count
197
    assert_equal 0, Thing4.count
198 199 200 201 202

    Thing1.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now)
    assert_equal 1, Thing1.count
    assert_equal 0, Thing2.count
    assert_equal 0, Thing3.count
203
    assert_equal 0, Thing4.count
204 205 206 207 208

    Thing2.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now)
    assert_equal 1, Thing1.count
    assert_equal 1, Thing2.count
    assert_equal 0, Thing3.count
209
    assert_equal 0, Thing4.count
210 211 212 213 214

    Thing3.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now)
    assert_equal 1, Thing1.count
    assert_equal 1, Thing2.count
    assert_equal 1, Thing3.count
215 216 217 218 219 220 221
    assert_equal 0, Thing4.count

    Thing4.create(:id => 1, :name => "thing1", :email => "thing1@localhost", :moment => Time.now)
    assert_equal 1, Thing1.count
    assert_equal 1, Thing2.count
    assert_equal 1, Thing3.count
    assert_equal 1, Thing4.count
222 223
  end

224
  def test_raise_on_unquoted_schema_name
225
    assert_raises(ActiveRecord::StatementInvalid) do
226 227 228 229 230
      with_schema_search_path '$user,public'
    end
  end

  def test_without_schema_search_path
231
    assert_raises(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
232 233 234 235 236 237
  end

  def test_ignore_nil_schema_search_path
    assert_nothing_raised { with_schema_search_path nil }
  end

238
  def test_dump_indexes_for_schema_one
239
    do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
240 241 242
  end

  def test_dump_indexes_for_schema_two
243
    do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
244 245
  end

246
  def test_dump_indexes_for_schema_multiple_schemas_in_search_path
A
Aaron Patterson 已提交
247
    do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
248 249
  end

250 251
  def test_with_uppercase_index_name
    ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
252
    assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"}
253 254 255

    ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
    ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME
256
    assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"}
257 258 259
    ActiveRecord::Base.connection.schema_search_path = "public"
  end

260
  def test_primary_key_with_schema_specified
261 262 263 264 265
    [
      %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
      %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
      %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
    ].each do |given|
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
      assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}"
    end
  end

  def test_primary_key_assuming_schema_search_path
    with_schema_search_path(SCHEMA_NAME) do
      assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found"
    end
  end

  def test_primary_key_raises_error_if_table_not_found_on_schema_search_path
    with_schema_search_path(SCHEMA2_NAME) do
      assert_raises(ActiveRecord::StatementInvalid) do
        @connection.primary_key(PK_TABLE_NAME)
      end
    end
  end

284 285 286
  def test_pk_and_sequence_for_with_schema_specified
    [
      %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
287
      %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
288 289 290
    ].each do |given|
      pk, seq = @connection.pk_and_sequence_for(given)
      assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
291 292
      assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
      assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given ==  %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
293 294 295
    end
  end

296 297
  def test_current_schema
    {
298 299
      %('$user',public)                        => 'public',
      SCHEMA_NAME                              => SCHEMA_NAME,
300 301 302 303 304 305 306
      %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME,
      %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public'
    }.each do |given,expect|
      with_schema_search_path(given) { assert_equal expect, @connection.current_schema }
    end
  end

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
  def test_prepared_statements_with_multiple_schemas

    @connection.schema_search_path = SCHEMA_NAME
    Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)

    @connection.schema_search_path = SCHEMA2_NAME
    Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now)

    @connection.schema_search_path = SCHEMA_NAME
    assert_equal 1, Thing5.count

    @connection.schema_search_path = SCHEMA2_NAME
    assert_equal 1, Thing5.count
  end

322 323 324 325 326 327 328 329 330 331 332
  def test_schema_exists?
    {
      'public'     => true,
      SCHEMA_NAME  => true,
      SCHEMA2_NAME => true,
      'darkside'   => false
    }.each do |given,expect|
      assert_equal expect, @connection.schema_exists?(given)
    end
  end

333 334 335 336 337 338 339 340 341 342 343 344 345
  private
    def columns(table_name)
      @connection.send(:column_definitions, table_name).map do |name, type, default|
        "#{name} #{type}" + (default ? " default #{default}" : '')
      end
    end

    def with_schema_search_path(schema_search_path)
      @connection.schema_search_path = schema_search_path
      yield if block_given?
    ensure
      @connection.schema_search_path = "'$user', public"
    end
346

347
    def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
348 349
      with_schema_search_path(this_schema_name) do
        indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
350
        assert_equal 3,indexes.size
351 352 353

        do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
        do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
354 355 356
        do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)

        assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
357 358 359 360 361 362 363 364 365
      end
    end

    def do_dump_index_assertions_for_one_index(this_index, this_index_name, this_index_column)
      assert_equal TABLE_NAME, this_index.table
      assert_equal 1, this_index.columns.size
      assert_equal this_index_column, this_index.columns[0]
      assert_equal this_index_name, this_index.name
    end
366
end