postgresql_adapter_test.rb 14.8 KB
Newer Older
1
require "cases/helper"
2 3
require "support/ddl_helper"
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
          configuration = ActiveRecord::Base.configurations["arunit"].merge(database: "should_not_exist-cinco-dog-db")
19
          connection = ActiveRecord::Base.postgresql_connection(configuration)
20
          connection.exec_query("SELECT 1")
21 22 23
        end
      end

24
      def test_valid_column
25
        with_example_table do
26
          column = @connection.columns("ex").find { |col| col.name == "id" }
27 28
          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
        with_example_table do
37
          assert_equal "id", @connection.primary_key("ex")
38
        end
39 40
      end

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

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

      def test_primary_key_returns_nil_for_no_pk
52 53
        with_example_table "id integer" do
          assert_nil @connection.primary_key("ex")
54
        end
55 56 57 58
      end

      def test_primary_key_raises_error_if_table_not_found
        assert_raises(ActiveRecord::StatementInvalid) do
59
          @connection.primary_key("unobtainium")
60 61 62
        end
      end

D
Doug Cole 已提交
63
      def test_exec_insert_with_returning_disabled
64
        connection = connection_without_insert_returning
65 66
        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
        connection = connection_without_insert_returning
72 73
        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
      def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
79 80
        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
81
        assert_equal expect.to_i, result.rows.first.first
82
      end
83 84 85

      def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
86 87
        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
88
        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
      def test_serial_sequence
98 99
        assert_equal "public.accounts_id_seq",
          @connection.serial_sequence("accounts", "id")
100 101

        assert_raises(ActiveRecord::StatementInvalid) do
102
          @connection.serial_sequence("zomg", "id")
103 104 105 106
        end
      end

      def test_default_sequence_name
107 108
        assert_equal "public.accounts_id_seq",
          @connection.default_sequence_name("accounts", "id")
109

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

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

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

122
      def test_pk_and_sequence_for
123
        with_example_table do
124 125 126
          pk, seq = @connection.pk_and_sequence_for("ex")
          assert_equal "id", pk
          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 134
        with_example_table "code serial primary key" do
          pk, seq = @connection.pk_and_sequence_for("ex")
          assert_equal "code", pk
          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
        with_example_table "id integer primary key" do
          assert_nil @connection.pk_and_sequence_for("ex")
141
        end
142 143 144
      end

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

      def test_pk_and_sequence_for_returns_nil_if_table_not_found
151
        assert_nil @connection.pk_and_sequence_for("unobtainium")
152 153
      end

154
      def test_pk_and_sequence_for_with_collision_pg_class_oid
155 156
        @connection.exec_query("create table ex(id serial primary key)")
        @connection.exec_query("create table ex2(id serial primary key)")
157 158 159 160

        correct_depend_record = [
          "'pg_class'::regclass",
          "'ex_id_seq'::regclass",
161
          "0",
162 163
          "'pg_class'::regclass",
          "'ex'::regclass",
164
          "1",
165 166 167 168 169 170
          "'a'"
        ]

        collision_depend_record = [
          "'pg_attrdef'::regclass",
          "'ex2_id_seq'::regclass",
171
          "0",
172 173
          "'pg_class'::regclass",
          "'ex'::regclass",
174
          "1",
175 176 177 178 179 180 181 182 183 184 185 186 187
          "'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(',')})"
        )

188
        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
        with_example_table do
207
          result = @connection.exec_query("SELECT id, data FROM ex")
208 209 210 211
          assert_equal 0, result.rows.length
          assert_equal 2, result.columns.length
          assert_equal %w{ id data }, result.columns

212
          string = @connection.quote("foo")
213
          @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
214
          result = @connection.exec_query("SELECT id, data FROM ex")
215 216 217
          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
      if ActiveRecord::Base.connection.prepared_statements
        def test_exec_with_binds
          with_example_table do
225
            string = @connection.quote("foo")
226
            @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
227

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

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

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

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

243
            bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
244
            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
            assert_equal [[1, "foo"]], result.rows
250
          end
251
        end
A
Aaron Patterson 已提交
252
      end
253

254
      def test_partial_index
255
        with_example_table do
256 257
          @connection.add_index "ex", %w{ id number }, :name => "partial", :where => "number > 100"
          index = @connection.indexes("ex").find { |idx| idx.name == "partial" }
258 259
          assert_equal "(number > 100)", index.where
        end
260 261
      end

262 263
      def test_expression_index
        with_example_table do
264 265 266
          @connection.add_index "ex", "mod(id, 10), abs(number)", name: "expression"
          index = @connection.indexes("ex").find { |idx| idx.name == "expression" }
          assert_equal "mod(id, 10), abs(number)", index.columns
267 268 269 270 271
        end
      end

      def test_index_with_opclass
        with_example_table do
272 273 274
          @connection.add_index "ex", "data varchar_pattern_ops", name: "with_opclass"
          index = @connection.indexes("ex").find { |idx| idx.name == "with_opclass" }
          assert_equal "data varchar_pattern_ops", index.columns
275 276 277
        end
      end

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
      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

293 294
      def test_columns_for_distinct_with_case
        assert_equal(
295 296
          "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",
297 298 299 300
            ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
        )
      end

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
      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

320 321 322 323 324 325 326 327 328 329 330
      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

331 332 333 334 335 336
      def test_raise_error_when_cannot_translate_exception
        assert_raise TypeError do
          @connection.send(:log, nil) { @connection.execute(nil) }
        end
      end

337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
      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

363 364 365 366 367 368 369 370 371 372 373
      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

374 375 376
      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
377
            self.table_name = "ex"
378 379 380 381 382 383 384 385 386 387 388 389 390
          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

391
      private
392

393 394
      def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block)
        super(@connection, "ex", definition, &block)
395 396
      end

397
      def connection_without_insert_returning
398
        ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations["arunit"].merge(:insert_returning => false))
399
      end
400 401 402
    end
  end
end