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

module ActiveRecord
  module ConnectionAdapters
7
    class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
8
      self.use_transactional_tests = false
9
      include DdlHelper
10
      include ConnectionHelper
11

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

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

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

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

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

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

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

      def test_primary_key_returns_nil_for_no_pk
52 53 54
        with_example_table 'id integer' do
          assert_nil @connection.primary_key('ex')
        end
55 56 57 58 59 60 61 62
      end

      def test_primary_key_raises_error_if_table_not_found
        assert_raises(ActiveRecord::StatementInvalid) do
          @connection.primary_key('unobtainium')
        end
      end

D
Doug Cole 已提交
63
      def test_exec_insert_with_returning_disabled
64 65 66
        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
67
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
68 69 70
      end

      def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
71 72 73
        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
74
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
75 76
      end

77 78 79 80 81
      def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
        result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], 'id')
        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
        assert_equal expect.to_i, result.rows.first.first
82
      end
83 84 85 86 87 88

      def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
        result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], 'id')
        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
        assert_equal expect.to_i, result.rows.first.first
89
      end
90

D
Doug Cole 已提交
91
      def test_sql_for_insert_with_returning_disabled
92
        connection = connection_without_insert_returning
93 94
        sql, binds = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
        assert_equal ['sql', 'binds'], [sql, binds]
D
Doug Cole 已提交
95 96
      end

97 98 99 100 101 102 103 104 105 106
      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
107
        assert_equal 'public.accounts_id_seq',
108 109
          @connection.default_sequence_name('accounts', 'id')

110
        assert_equal 'public.accounts_id_seq',
111 112 113 114
          @connection.default_sequence_name('accounts')
      end

      def test_default_sequence_name_bad_table
115
        assert_equal 'zomg_id_seq',
116 117
          @connection.default_sequence_name('zomg', 'id')

118
        assert_equal 'zomg_id_seq',
119 120 121
          @connection.default_sequence_name('zomg')
      end

122
      def test_pk_and_sequence_for
123 124 125
        with_example_table do
          pk, seq = @connection.pk_and_sequence_for('ex')
          assert_equal 'id', pk
126
          assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s
127
        end
128 129 130
      end

      def test_pk_and_sequence_for_with_non_standard_primary_key
131 132 133
        with_example_table 'code serial primary key' do
          pk, seq = @connection.pk_and_sequence_for('ex')
          assert_equal 'code', pk
134
          assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s
135
        end
136 137 138
      end

      def test_pk_and_sequence_for_returns_nil_if_no_seq
139 140 141
        with_example_table 'id integer primary key' do
          assert_nil @connection.pk_and_sequence_for('ex')
        end
142 143 144
      end

      def test_pk_and_sequence_for_returns_nil_if_no_pk
145 146 147
        with_example_table 'id integer' do
          assert_nil @connection.pk_and_sequence_for('ex')
        end
148 149 150 151 152 153
      end

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

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
      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
189
        assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq
190 191 192 193 194

        @connection.exec_query(
          "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
        )
      ensure
195 196
        @connection.drop_table 'ex', if_exists: true
        @connection.drop_table 'ex2', if_exists: true
197 198
      end

199 200 201 202 203
      def test_table_alias_length
        assert_nothing_raised do
          @connection.table_alias_length
        end
      end
204 205

      def test_exec_no_binds
206 207 208 209 210 211 212 213 214 215 216 217
        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

218
          assert_equal [[1, 'foo']], result.rows
219
        end
220
      end
221

222 223 224 225 226
      if ActiveRecord::Base.connection.prepared_statements
        def test_exec_with_binds
          with_example_table do
            string = @connection.quote('foo')
            @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
227

228 229
            bind = Relation::QueryAttribute.new("id", 1, Type::Value.new)
            result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
230

231 232 233 234 235
            assert_equal 1, result.rows.length
            assert_equal 2, result.columns.length

            assert_equal [[1, 'foo']], result.rows
          end
236
        end
A
Aaron Patterson 已提交
237

238 239 240 241
        def test_exec_typecasts_bind_vals
          with_example_table do
            string = @connection.quote('foo')
            @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
A
Aaron Patterson 已提交
242

243 244
            bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
            result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
A
Aaron Patterson 已提交
245

246 247
            assert_equal 1, result.rows.length
            assert_equal 2, result.columns.length
A
Aaron Patterson 已提交
248

249 250
            assert_equal [[1, 'foo']], result.rows
          end
251
        end
A
Aaron Patterson 已提交
252
      end
253

254
      def test_partial_index
255 256 257 258 259
        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
260 261
      end

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
      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

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

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
      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

304 305 306 307 308 309 310 311 312 313 314
      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

315 316 317 318 319 320
      def test_raise_error_when_cannot_translate_exception
        assert_raise TypeError do
          @connection.send(:log, nil) { @connection.execute(nil) }
        end
      end

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
      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

347 348 349 350 351 352 353 354 355 356 357
      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

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
      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

375
      private
376

377 378
      def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
        super(@connection, 'ex', definition, &block)
379 380
      end

381 382 383
      def connection_without_insert_returning
        ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
      end
384 385 386
    end
  end
end