schema_test.rb 22.6 KB
Newer Older
1
require "cases/helper"
2
require 'models/default'
3
require 'support/schema_dumping_helper'
4

5 6 7
module PGSchemaHelper
  def with_schema_search_path(schema_search_path)
    @connection.schema_search_path = schema_search_path
8
    @connection.schema_cache.clear!
9 10 11
    yield if block_given?
  ensure
    @connection.schema_search_path = "'$user', public"
12
    @connection.schema_cache.clear!
13 14 15
  end
end

16
class SchemaTest < ActiveRecord::PostgreSQLTestCase
17
  include PGSchemaHelper
18
  self.use_transactional_tests = false
19

20
  SCHEMA_NAME = 'test_schema'
21
  SCHEMA2_NAME = 'test_schema2'
22
  TABLE_NAME = 'things'
23
  CAPITALIZED_TABLE_NAME = 'Things'
24 25
  INDEX_A_NAME = 'a_index_things_on_name'
  INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
26
  INDEX_C_NAME = 'c_index_full_text_search'
27
  INDEX_D_NAME = 'd_index_things_on_description_desc'
D
doabit 已提交
28
  INDEX_E_NAME = 'e_index_things_on_name_vector'
29 30 31
  INDEX_A_COLUMN = 'name'
  INDEX_B_COLUMN_S1 = 'email'
  INDEX_B_COLUMN_S2 = 'moment'
32
  INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
33
  INDEX_D_COLUMN = 'description'
D
doabit 已提交
34
  INDEX_E_COLUMN = 'name_vector'
35 36 37
  COLUMNS = [
    'id integer',
    'name character varying(50)',
38
    'email character varying(50)',
39
    'description character varying(100)',
D
doabit 已提交
40
    'name_vector tsvector',
41 42
    'moment timestamp without time zone default now()'
  ]
43
  PK_TABLE_NAME = 'table_with_pk'
44 45
  UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq'
  UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk'
46

47
  class Thing1 < ActiveRecord::Base
48
    self.table_name = "test_schema.things"
49 50 51
  end

  class Thing2 < ActiveRecord::Base
52
    self.table_name = "test_schema2.things"
53 54 55
  end

  class Thing3 < ActiveRecord::Base
56
    self.table_name = 'test_schema."things.table"'
57 58
  end

59
  class Thing4 < ActiveRecord::Base
60
    self.table_name = 'test_schema."Things"'
61 62
  end

63
  class Thing5 < ActiveRecord::Base
64
    self.table_name = 'things'
65 66
  end

67 68 69 70 71 72 73 74 75 76
  class Song < ActiveRecord::Base
    self.table_name = "music.songs"
    has_and_belongs_to_many :albums
  end

  class Album < ActiveRecord::Base
    self.table_name = "music.albums"
    has_and_belongs_to_many :songs
  end

77 78 79
  def setup
    @connection = ActiveRecord::Base.connection
    @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
80
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})"
81
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{CAPITALIZED_TABLE_NAME}\" (#{COLUMNS.join(',')})"
82 83 84 85 86
    @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});"
87 88
    @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});"
89 90
    @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);"
D
doabit 已提交
91 92
    @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME}  USING gin (#{INDEX_E_COLUMN});"
    @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME}  USING gin (#{INDEX_E_COLUMN});"
93
    @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
94 95
    @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))"
96 97
  end

G
Guo Xiang Tan 已提交
98
  teardown do
99 100
    @connection.drop_schema SCHEMA2_NAME, if_exists: true
    @connection.drop_schema SCHEMA_NAME, if_exists: true
101 102
  end

103
  def test_schema_names
104
    assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
105 106
  end

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
  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
D
dfens 已提交
129
      @connection.create_schema "test_schema3"
130 131 132 133 134 135
    ensure
      @connection.drop_schema "test_schema3"
    end
    assert !@connection.schema_names.include?("test_schema3")
  end

136 137 138 139 140 141 142
  def test_drop_schema_if_exists
    @connection.create_schema "some_schema"
    assert_includes @connection.schema_names, "some_schema"
    @connection.drop_schema "some_schema", if_exists: true
    assert_not_includes @connection.schema_names, "some_schema"
  end

143
  def test_habtm_table_name_with_schema
144 145
    ActiveRecord::Base.connection.drop_schema "music", if_exists: true
    ActiveRecord::Base.connection.create_schema "music"
146 147 148 149 150 151 152
    ActiveRecord::Base.connection.execute <<-SQL
      CREATE TABLE music.albums (id serial primary key);
      CREATE TABLE music.songs (id serial primary key);
      CREATE TABLE music.albums_songs (album_id integer, song_id integer);
    SQL

    song = Song.create
153
    Album.create
154 155
    assert_equal song, Song.includes(:albums).references(:albums).first
  ensure
156
    ActiveRecord::Base.connection.drop_schema "music", if_exists: true
157 158
  end

159
  def test_drop_schema_with_nonexisting_schema
160
    assert_raises(ActiveRecord::StatementInvalid) do
161 162 163 164 165
      @connection.drop_schema "idontexist"
    end

    assert_nothing_raised do
      @connection.drop_schema "idontexist", if_exists: true
166 167 168
    end
  end

169 170
  def test_raise_wraped_exception_on_bad_prepare
    assert_raises(ActiveRecord::StatementInvalid) do
171
      @connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)]
172 173 174
    end
  end

175
  def test_schema_change_with_prepared_stmt
J
Jon Leighton 已提交
176
    altered = false
S
Sean Griffin 已提交
177
    @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
178
    @connection.exec_query "alter table developers add column zomg int", 'sql', []
J
Jon Leighton 已提交
179
    altered = true
S
Sean Griffin 已提交
180
    @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
181
  ensure
J
Jon Leighton 已提交
182 183 184
    # 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
185 186
  end

187
  def test_data_source_exists?
188 189
    [Thing1, Thing2, Thing3, Thing4].each do |klass|
      name = klass.table_name
190
      assert @connection.data_source_exists?(name), "'#{name}' data_source should exist"
191 192 193
    end
  end

194
  def test_data_source_exists_when_on_schema_search_path
195
    with_schema_search_path(SCHEMA_NAME) do
196
      assert(@connection.data_source_exists?(TABLE_NAME), "data_source should exist and be found")
197 198 199
    end
  end

200
  def test_data_source_exists_when_not_on_schema_search_path
201
    with_schema_search_path('PUBLIC') do
202
      assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found")
203 204 205
    end
  end

206 207
  def test_data_source_exists_wrong_schema
    assert(!@connection.data_source_exists?("foo.things"), "data_source should not exist")
208 209
  end

210
  def test_data_source_exists_quoted_names
211
    [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given|
212
      assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
213 214 215
    end
    with_schema_search_path(SCHEMA_NAME) do
      given = %("#{TABLE_NAME}")
216
      assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
217
    end
218 219
  end

220
  def test_data_source_exists_quoted_table
221
    with_schema_search_path(SCHEMA_NAME) do
222
      assert(@connection.data_source_exists?('"things.table"'), "data_source should exist")
223 224 225
    end
  end

226 227 228 229 230 231
  def test_with_schema_prefixed_table_name
    assert_nothing_raised do
      assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}")
    end
  end

232 233 234 235 236 237
  def test_with_schema_prefixed_capitalized_table_name
    assert_nothing_raised do
      assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{CAPITALIZED_TABLE_NAME}")
    end
  end

238 239 240 241 242 243 244 245
  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

246 247 248 249 250 251 252 253 254 255 256 257 258
  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
259
    assert_equal 0, Thing4.count
260 261 262 263 264

    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
265
    assert_equal 0, Thing4.count
266 267 268 269 270

    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
271
    assert_equal 0, Thing4.count
272 273 274 275 276

    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
277 278 279 280 281 282 283
    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
284 285
  end

286
  def test_raise_on_unquoted_schema_name
287
    assert_raises(ActiveRecord::StatementInvalid) do
288 289 290 291 292
      with_schema_search_path '$user,public'
    end
  end

  def test_without_schema_search_path
293
    assert_raises(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
294 295 296 297 298 299
  end

  def test_ignore_nil_schema_search_path
    assert_nothing_raised { with_schema_search_path nil }
  end

300 301 302 303 304 305 306 307 308 309 310 311
  def test_index_name_exists
    with_schema_search_path(SCHEMA_NAME) do
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true)
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true)
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true)
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true)
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
      assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
      assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true)
    end
  end

312
  def test_dump_indexes_for_schema_one
D
doabit 已提交
313
    do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
314 315 316
  end

  def test_dump_indexes_for_schema_two
D
doabit 已提交
317
    do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN)
318 319
  end

320
  def test_dump_indexes_for_schema_multiple_schemas_in_search_path
D
doabit 已提交
321
    do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
322 323
  end

324 325 326 327 328
  def test_dump_indexes_for_table_with_scheme_specified_in_name
    indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}")
    assert_equal 4, indexes.size
  end

329
  def test_with_uppercase_index_name
G
Guo Xiang Tan 已提交
330
    @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
331

G
Guo Xiang Tan 已提交
332
    with_schema_search_path SCHEMA_NAME do
333
      assert_nothing_raised { @connection.remove_index "things", name: "things_Index"}
G
Guo Xiang Tan 已提交
334
    end
335 336
  end

337 338 339 340 341 342 343 344 345 346 347 348 349 350
  def test_remove_index_when_schema_specified
    @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
    assert_nothing_raised { @connection.remove_index "things", name: "#{SCHEMA_NAME}.things_Index" }

    @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
    assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.things", name: "things_Index" }

    @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
    assert_nothing_raised { @connection.remove_index "#{SCHEMA_NAME}.things", name: "#{SCHEMA_NAME}.things_Index" }

    @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
    assert_raises(ArgumentError) { @connection.remove_index "#{SCHEMA2_NAME}.things", name: "#{SCHEMA_NAME}.things_Index" }
  end

351
  def test_primary_key_with_schema_specified
352 353 354 355 356
    [
      %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
      %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
      %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
    ].each do |given|
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
      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

375
  def test_pk_and_sequence_for_with_schema_specified
376
    pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
377 378
    [
      %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
379
      %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
380 381 382
    ].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}"
383 384
      assert_equal pg_name.new(SCHEMA_NAME, "#{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 pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given ==  %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
385 386 387
    end
  end

388 389
  def test_current_schema
    {
390 391
      %('$user',public)                        => 'public',
      SCHEMA_NAME                              => SCHEMA_NAME,
392 393 394 395 396 397 398
      %(#{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

399
  def test_prepared_statements_with_multiple_schemas
G
Guo Xiang Tan 已提交
400 401 402 403 404
    [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
      with_schema_search_path schema_name do
        Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
      end
    end
405

G
Guo Xiang Tan 已提交
406 407 408 409 410
    [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
      with_schema_search_path schema_name do
        assert_equal 1, Thing5.count
      end
    end
411 412
  end

413 414 415 416 417 418 419 420 421 422 423
  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

424 425 426
  def test_reset_pk_sequence
    sequence_name = "#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
    @connection.execute "SELECT setval('#{sequence_name}', 123)"
427
    assert_equal 124, @connection.select_value("SELECT nextval('#{sequence_name}')")
428
    @connection.reset_pk_sequence!("#{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME}")
429
    assert_equal 1, @connection.select_value("SELECT nextval('#{sequence_name}')")
430 431
  end

432 433 434 435
  def test_set_pk_sequence
    table_name = "#{SCHEMA_NAME}.#{PK_TABLE_NAME}"
    _, sequence_name = @connection.pk_and_sequence_for table_name
    @connection.set_pk_sequence! table_name, 123
436
    assert_equal 124, @connection.select_value("SELECT nextval('#{sequence_name}')")
437 438 439
    @connection.reset_pk_sequence! table_name
  end

440 441 442 443 444 445 446
  private
    def columns(table_name)
      @connection.send(:column_definitions, table_name).map do |name, type, default|
        "#{name} #{type}" + (default ? " default #{default}" : '')
      end
    end

D
doabit 已提交
447
    def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
448
      with_schema_search_path(this_schema_name) do
449
        indexes = @connection.indexes(TABLE_NAME).sort_by(&:name)
D
doabit 已提交
450
        assert_equal 4,indexes.size
451 452 453

        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)
454
        do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
D
doabit 已提交
455
        do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
456

D
doabit 已提交
457 458 459 460
        indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
           assert_equal :btree, index.using
        end
        assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
461
        assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
462 463 464 465 466 467 468 469 470
      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
S
Sean Griffin 已提交
471 472 473 474

    def bind_param(value)
      ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
    end
475
end
476

477
class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
  include SchemaDumpingHelper

  setup do
    @connection = ActiveRecord::Base.connection
  end

  def test_dump_foreign_key_targeting_different_schema
    @connection.create_schema "my_schema"
    @connection.create_table "my_schema.trains" do |t|
      t.string :name
    end
    @connection.create_table "wagons" do |t|
      t.integer :train_id
    end
    @connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id"
    output = dump_table_schema "wagons"
494
    assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output
495
  ensure
496 497
    @connection.drop_table "wagons", if_exists: true
    @connection.drop_table "my_schema.trains", if_exists: true
498
    @connection.drop_schema "my_schema", if_exists: true
499 500
  end
end
501

502
class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase
503 504
  setup do
    @connection = ActiveRecord::Base.connection
505
    @connection.drop_schema "schema_1", if_exists: true
506 507 508 509 510 511 512 513 514 515
    @connection.execute "CREATE SCHEMA schema_1"
    @connection.execute "CREATE DOMAIN schema_1.text AS text"
    @connection.execute "CREATE DOMAIN schema_1.varchar AS varchar"
    @connection.execute "CREATE DOMAIN schema_1.bpchar AS bpchar"

    @old_search_path = @connection.schema_search_path
    @connection.schema_search_path = "schema_1, pg_catalog"
    @connection.create_table "defaults" do |t|
      t.text "text_col", default: "some value"
      t.string "string_col", default: "some value"
516
      t.decimal "decimal_col", default: "3.14159265358979323846"
517 518 519 520 521 522
    end
    Default.reset_column_information
  end

  teardown do
    @connection.schema_search_path = @old_search_path
523
    @connection.drop_schema "schema_1", if_exists: true
524 525 526 527 528 529 530 531 532 533 534
    Default.reset_column_information
  end

  def test_text_defaults_in_new_schema_when_overriding_domain
    assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parsed"
  end

  def test_string_defaults_in_new_schema_when_overriding_domain
    assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parsed"
  end

535 536 537 538
  def test_decimal_defaults_in_new_schema_when_overriding_domain
    assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
  end

539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
  def test_bpchar_defaults_in_new_schema_when_overriding_domain
    @connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'"
    Default.reset_column_information
    assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parsed"
  end

  def test_text_defaults_after_updating_column_default
    @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text"
    assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parsed after updating default using '::text' since postgreSQL will add parens to the default in db"
  end

  def test_default_containing_quote_and_colons
    @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
    assert_equal "foo'::bar", Default.new.string_col
  end
end
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591

class SchemaWithDotsTest < ActiveRecord::PostgreSQLTestCase
  include PGSchemaHelper
  self.use_transactional_tests = false

  setup do
    @connection = ActiveRecord::Base.connection
    @connection.create_schema "my.schema"
  end

  teardown do
    @connection.drop_schema "my.schema", if_exists: true
  end

  test "rename_table" do
    with_schema_search_path('"my.schema"') do
      @connection.create_table :posts
      @connection.rename_table :posts, :articles
      assert_equal ["articles"], @connection.tables
    end
  end

  test "Active Record basics" do
    with_schema_search_path('"my.schema"') do
      @connection.create_table :articles do |t|
        t.string :title
      end
      article_class = Class.new(ActiveRecord::Base) do
        self.table_name = '"my.schema".articles'
      end

      article_class.create!(title: "zOMG, welcome to my blorgh!")
      welcome_article = article_class.last
      assert_equal "zOMG, welcome to my blorgh!", welcome_article.title
    end
  end
end