postgresql_adapter_test.rb 16.1 KB
Newer Older
1
require "cases/helper"
2
require 'support/ddl_helper'
3
require 'support/connection_helper'
4 5 6 7

module ActiveRecord
  module ConnectionAdapters
    class PostgreSQLAdapterTest < ActiveRecord::TestCase
8
      include DdlHelper
9
      include ConnectionHelper
10

11 12
      def setup
        @connection = ActiveRecord::Base.connection
13 14
      end

15 16
      def test_bad_connection
        assert_raise ActiveRecord::NoDatabaseError do
17 18
          configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db')
          connection = ActiveRecord::Base.postgresql_connection(configuration)
19
          connection.exec_query('SELECT 1')
20 21 22
        end
      end

23
      def test_valid_column
24 25 26 27
        with_example_table do
          column = @connection.columns('ex').find { |col| col.name == 'id' }
          assert @connection.valid_type?(column.type)
        end
28 29 30 31 32 33
      end

      def test_invalid_column
        assert_not @connection.valid_type?(:foobar)
      end

34
      def test_primary_key
35 36 37
        with_example_table do
          assert_equal 'id', @connection.primary_key('ex')
        end
38 39
      end

40 41 42 43
      def test_primary_key_works_tables_containing_capital_letters
        assert_equal 'id', @connection.primary_key('CamelCase')
      end

44
      def test_non_standard_primary_key
45 46 47
        with_example_table 'data character varying(255) primary key' do
          assert_equal 'data', @connection.primary_key('ex')
        end
48 49 50
      end

      def test_primary_key_returns_nil_for_no_pk
51 52 53
        with_example_table 'id integer' do
          assert_nil @connection.primary_key('ex')
        end
54 55
      end

56 57 58 59 60 61
      def test_composite_primary_key
        with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do
          assert_nil @connection.primary_key('ex')
        end
      end

62 63 64 65 66 67 68
      def test_primary_key_raises_error_if_table_not_found
        assert_raises(ActiveRecord::StatementInvalid) do
          @connection.primary_key('unobtainium')
        end
      end

      def test_insert_sql_with_proprietary_returning_clause
69 70
        with_example_table do
          id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
71
          assert_equal 5150, id
72
        end
73 74 75
      end

      def test_insert_sql_with_quoted_schema_and_table_name
76 77 78 79 80
        with_example_table do
          id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
          expect = @connection.query('select max(id) from ex').first.first
          assert_equal expect, id
        end
81 82 83
      end

      def test_insert_sql_with_no_space_after_table_name
84 85 86 87 88
        with_example_table do
          id = @connection.insert_sql("insert into ex(number) values(5150)")
          expect = @connection.query('select max(id) from ex').first.first
          assert_equal expect, id
        end
89 90
      end

91
      def test_multiline_insert_sql
92 93 94 95 96 97 98 99 100 101 102
        with_example_table do
          id = @connection.insert_sql(<<-SQL)
          insert into ex(
                         number)
          values(
                 5152
                 )
          SQL
          expect = @connection.query('select max(id) from ex').first.first
          assert_equal expect, id
        end
103 104
      end

105
      def test_insert_sql_with_returning_disabled
106 107 108
        connection = connection_without_insert_returning
        id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
109
        assert_equal expect.to_i, id
110 111
      end

D
Doug Cole 已提交
112
      def test_exec_insert_with_returning_disabled
113 114 115
        connection = connection_without_insert_returning
        result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq')
        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
116
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
117 118 119
      end

      def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
120 121 122
        connection = connection_without_insert_returning
        result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id')
        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
123
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
124 125 126
      end

      def test_sql_for_insert_with_returning_disabled
127 128
        connection = connection_without_insert_returning
        result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
D
Doug Cole 已提交
129 130 131
        assert_equal ['sql', 'binds'], result
      end

132 133 134 135 136 137 138 139 140 141
      def test_serial_sequence
        assert_equal 'public.accounts_id_seq',
          @connection.serial_sequence('accounts', 'id')

        assert_raises(ActiveRecord::StatementInvalid) do
          @connection.serial_sequence('zomg', 'id')
        end
      end

      def test_default_sequence_name
142
        assert_equal 'public.accounts_id_seq',
143 144
          @connection.default_sequence_name('accounts', 'id')

145
        assert_equal 'public.accounts_id_seq',
146 147 148 149
          @connection.default_sequence_name('accounts')
      end

      def test_default_sequence_name_bad_table
150
        assert_equal 'zomg_id_seq',
151 152
          @connection.default_sequence_name('zomg', 'id')

153
        assert_equal 'zomg_id_seq',
154 155 156
          @connection.default_sequence_name('zomg')
      end

157
      def test_pk_and_sequence_for
158 159 160
        with_example_table do
          pk, seq = @connection.pk_and_sequence_for('ex')
          assert_equal 'id', pk
161
          assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s
162
        end
163 164 165
      end

      def test_pk_and_sequence_for_with_non_standard_primary_key
166 167 168
        with_example_table 'code serial primary key' do
          pk, seq = @connection.pk_and_sequence_for('ex')
          assert_equal 'code', pk
169
          assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s
170
        end
171 172 173
      end

      def test_pk_and_sequence_for_returns_nil_if_no_seq
174 175 176
        with_example_table 'id integer primary key' do
          assert_nil @connection.pk_and_sequence_for('ex')
        end
177 178 179
      end

      def test_pk_and_sequence_for_returns_nil_if_no_pk
180 181 182
        with_example_table 'id integer' do
          assert_nil @connection.pk_and_sequence_for('ex')
        end
183 184 185 186 187 188
      end

      def test_pk_and_sequence_for_returns_nil_if_table_not_found
        assert_nil @connection.pk_and_sequence_for('unobtainium')
      end

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
      def test_pk_and_sequence_for_with_collision_pg_class_oid
        @connection.exec_query('create table ex(id serial primary key)')
        @connection.exec_query('create table ex2(id serial primary key)')

        correct_depend_record = [
          "'pg_class'::regclass",
          "'ex_id_seq'::regclass",
          '0',
          "'pg_class'::regclass",
          "'ex'::regclass",
          '1',
          "'a'"
        ]

        collision_depend_record = [
          "'pg_attrdef'::regclass",
          "'ex2_id_seq'::regclass",
          '0',
          "'pg_class'::regclass",
          "'ex'::regclass",
          '1',
          "'a'"
        ]

        @connection.exec_query(
          "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
        )
        @connection.exec_query(
          "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})"
        )
        @connection.exec_query(
          "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})"
        )

        seq = @connection.pk_and_sequence_for('ex').last
224
        assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq
225 226 227 228 229

        @connection.exec_query(
          "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
        )
      ensure
230 231
        @connection.drop_table 'ex', if_exists: true
        @connection.drop_table 'ex2', if_exists: true
232 233
      end

234
      def test_exec_insert_number
235 236
        with_example_table do
          insert(@connection, 'number' => 10)
237

238
          result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
239

240
          assert_equal 1, result.rows.length
241
          assert_equal 10, result.rows.last.last
242
        end
243 244 245
      end

      def test_exec_insert_string
246 247 248
        with_example_table do
          str = 'いただきます!'
          insert(@connection, 'number' => 10, 'data' => str)
249

250
          result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
251

252
          value = result.rows.last.last
253

254 255
          assert_equal str, value
        end
256 257 258 259 260 261 262
      end

      def test_table_alias_length
        assert_nothing_raised do
          @connection.table_alias_length
        end
      end
263 264

      def test_exec_no_binds
265 266 267 268 269 270 271 272 273 274 275 276
        with_example_table do
          result = @connection.exec_query('SELECT id, data FROM ex')
          assert_equal 0, result.rows.length
          assert_equal 2, result.columns.length
          assert_equal %w{ id data }, result.columns

          string = @connection.quote('foo')
          @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
          result = @connection.exec_query('SELECT id, data FROM ex')
          assert_equal 1, result.rows.length
          assert_equal 2, result.columns.length

277
          assert_equal [[1, 'foo']], result.rows
278
        end
279
      end
280 281

      def test_exec_with_binds
282 283 284 285
        with_example_table do
          string = @connection.quote('foo')
          @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
          result = @connection.exec_query(
S
Sean Griffin 已提交
286
                                          'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
287

288 289
          assert_equal 1, result.rows.length
          assert_equal 2, result.columns.length
290

291
          assert_equal [[1, 'foo']], result.rows
292
        end
293
      end
A
Aaron Patterson 已提交
294 295

      def test_exec_typecasts_bind_vals
296 297 298
        with_example_table do
          string = @connection.quote('foo')
          @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
A
Aaron Patterson 已提交
299

S
Sean Griffin 已提交
300
          bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
301
          result = @connection.exec_query(
S
Sean Griffin 已提交
302
                                          'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
A
Aaron Patterson 已提交
303

304 305
          assert_equal 1, result.rows.length
          assert_equal 2, result.columns.length
A
Aaron Patterson 已提交
306

307
          assert_equal [[1, 'foo']], result.rows
308
        end
A
Aaron Patterson 已提交
309
      end
310

311
      def test_substitute_at
312
        bind = @connection.substitute_at(nil)
S
Sean Griffin 已提交
313
        assert_equal Arel.sql('$1'), bind.to_sql
314
      end
315

316
      def test_partial_index
317 318 319 320 321
        with_example_table do
          @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
          index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
          assert_equal "(number > 100)", index.where
        end
322 323
      end

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
      def test_columns_for_distinct_zero_orders
        assert_equal "posts.id",
          @connection.columns_for_distinct("posts.id", [])
      end

      def test_columns_for_distinct_one_order
        assert_equal "posts.id, posts.created_at AS alias_0",
          @connection.columns_for_distinct("posts.id", ["posts.created_at desc"])
      end

      def test_columns_for_distinct_few_orders
        assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
          @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
      end

339 340 341 342 343 344 345 346
      def test_columns_for_distinct_with_case
        assert_equal(
          'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0',
          @connection.columns_for_distinct('posts.id',
            ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
        )
      end

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
      def test_columns_for_distinct_blank_not_nil_orders
        assert_equal "posts.id, posts.created_at AS alias_0",
          @connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", "   "])
      end

      def test_columns_for_distinct_with_arel_order
        order = Object.new
        def order.to_sql
          "posts.created_at desc"
        end
        assert_equal "posts.id, posts.created_at AS alias_0",
          @connection.columns_for_distinct("posts.id", [order])
      end

      def test_columns_for_distinct_with_nulls
        assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"])
        assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
      end

366 367 368 369 370 371 372 373 374 375 376
      def test_columns_for_distinct_without_order_specifiers
        assert_equal "posts.title, posts.updater_id AS alias_0",
          @connection.columns_for_distinct("posts.title", ["posts.updater_id"])

        assert_equal "posts.title, posts.updater_id AS alias_0",
          @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"])

        assert_equal "posts.title, posts.updater_id AS alias_0",
          @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"])
      end

377 378 379 380 381 382
      def test_raise_error_when_cannot_translate_exception
        assert_raise TypeError do
          @connection.send(:log, nil) { @connection.execute(nil) }
        end
      end

383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
      def test_reload_type_map_for_newly_defined_types
        @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
        result = @connection.select_all "SELECT 'good'::feeling"
        assert_instance_of(PostgreSQLAdapter::OID::Enum,
                           result.column_types["feeling"])
      ensure
        @connection.execute "DROP TYPE IF EXISTS feeling"
        reset_connection
      end

      def test_only_reload_type_map_once_for_every_unknown_type
        silence_warnings do
          assert_queries 2, ignore_none: true do
            @connection.select_all "SELECT NULL::anyelement"
          end
          assert_queries 1, ignore_none: true do
            @connection.select_all "SELECT NULL::anyelement"
          end
          assert_queries 2, ignore_none: true do
            @connection.select_all "SELECT NULL::anyarray"
          end
        end
      ensure
        reset_connection
      end

409 410 411 412 413 414 415 416 417 418 419
      def test_only_warn_on_first_encounter_of_unknown_oid
        warning = capture(:stderr) {
          @connection.select_all "SELECT NULL::anyelement"
          @connection.select_all "SELECT NULL::anyelement"
          @connection.select_all "SELECT NULL::anyelement"
        }
        assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
      ensure
        reset_connection
      end

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
      def test_unparsed_defaults_are_at_least_set_when_saving
        with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do
          number_klass = Class.new(ActiveRecord::Base) do
            self.table_name = 'ex'
          end
          column = number_klass.columns_hash["number"]
          assert_nil column.default
          assert_nil column.default_function

          first_number = number_klass.new
          assert_nil first_number.number

          first_number.save!
          assert_equal 4, first_number.reload.number
        end
      end

437 438
      private
      def insert(ctx, data)
S
Sean Griffin 已提交
439 440
        binds = data.map { |name, value|
          bind_param(value, name)
441
        }
S
Sean Griffin 已提交
442
        columns = binds.map(&:name)
443 444 445 446 447 448 449 450

        bind_subs = columns.length.times.map { |x| "$#{x + 1}" }

        sql = "INSERT INTO ex (#{columns.join(", ")})
               VALUES (#{bind_subs.join(', ')})"

        ctx.exec_insert(sql, 'SQL', binds)
      end
451

452 453
      def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
        super(@connection, 'ex', definition, &block)
454 455
      end

456 457 458
      def connection_without_insert_returning
        ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
      end
S
Sean Griffin 已提交
459 460 461 462

      def bind_param(value, name = nil)
        ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
      end
463 464 465
    end
  end
end