postgresql_adapter_test.rb 14.2 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_primary_key
25
        with_example_table do
26
          assert_equal "id", @connection.primary_key("ex")
27
        end
28 29
      end

30
      def test_primary_key_works_tables_containing_capital_letters
31
        assert_equal "id", @connection.primary_key("CamelCase")
32 33
      end

34
      def test_non_standard_primary_key
35 36
        with_example_table "data character varying(255) primary key" do
          assert_equal "data", @connection.primary_key("ex")
37
        end
38 39 40
      end

      def test_primary_key_returns_nil_for_no_pk
41 42
        with_example_table "id integer" do
          assert_nil @connection.primary_key("ex")
43
        end
44 45
      end

D
Doug Cole 已提交
46
      def test_exec_insert_with_returning_disabled
47
        connection = connection_without_insert_returning
48 49
        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
50
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
51 52 53
      end

      def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
54
        connection = connection_without_insert_returning
55 56
        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
57
        assert_equal expect.to_i, result.rows.first.first
D
Doug Cole 已提交
58 59
      end

60 61
      def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
62 63
        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
64
        assert_equal expect.to_i, result.rows.first.first
65
      end
66 67 68

      def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given
        connection = connection_without_insert_returning
69 70
        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
71
        assert_equal expect.to_i, result.rows.first.first
72
      end
73

74
      def test_serial_sequence
75 76
        assert_equal "public.accounts_id_seq",
          @connection.serial_sequence("accounts", "id")
77 78

        assert_raises(ActiveRecord::StatementInvalid) do
79
          @connection.serial_sequence("zomg", "id")
80 81 82 83
        end
      end

      def test_default_sequence_name
84 85
        assert_equal "public.accounts_id_seq",
          @connection.default_sequence_name("accounts", "id")
86

87 88
        assert_equal "public.accounts_id_seq",
          @connection.default_sequence_name("accounts")
89 90 91
      end

      def test_default_sequence_name_bad_table
92 93
        assert_equal "zomg_id_seq",
          @connection.default_sequence_name("zomg", "id")
94

95 96
        assert_equal "zomg_id_seq",
          @connection.default_sequence_name("zomg")
97 98
      end

99
      def test_pk_and_sequence_for
100
        with_example_table do
101 102 103
          pk, seq = @connection.pk_and_sequence_for("ex")
          assert_equal "id", pk
          assert_equal @connection.default_sequence_name("ex", "id"), seq.to_s
104
        end
105 106 107
      end

      def test_pk_and_sequence_for_with_non_standard_primary_key
108 109 110 111
        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
112
        end
113 114 115
      end

      def test_pk_and_sequence_for_returns_nil_if_no_seq
116 117
        with_example_table "id integer primary key" do
          assert_nil @connection.pk_and_sequence_for("ex")
118
        end
119 120 121
      end

      def test_pk_and_sequence_for_returns_nil_if_no_pk
122 123
        with_example_table "id integer" do
          assert_nil @connection.pk_and_sequence_for("ex")
124
        end
125 126 127
      end

      def test_pk_and_sequence_for_returns_nil_if_table_not_found
128
        assert_nil @connection.pk_and_sequence_for("unobtainium")
129 130
      end

131
      def test_pk_and_sequence_for_with_collision_pg_class_oid
132 133
        @connection.exec_query("create table ex(id serial primary key)")
        @connection.exec_query("create table ex2(id serial primary key)")
134 135 136 137

        correct_depend_record = [
          "'pg_class'::regclass",
          "'ex_id_seq'::regclass",
138
          "0",
139 140
          "'pg_class'::regclass",
          "'ex'::regclass",
141
          "1",
142 143 144 145 146 147
          "'a'"
        ]

        collision_depend_record = [
          "'pg_attrdef'::regclass",
          "'ex2_id_seq'::regclass",
148
          "0",
149 150
          "'pg_class'::regclass",
          "'ex'::regclass",
151
          "1",
152 153 154 155 156 157 158 159 160 161 162 163 164
          "'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(',')})"
        )

165
        seq = @connection.pk_and_sequence_for("ex").last
166
        assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq
167 168 169 170 171

        @connection.exec_query(
          "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
        )
      ensure
172 173
        @connection.drop_table "ex", if_exists: true
        @connection.drop_table "ex2", if_exists: true
174 175
      end

176 177 178 179 180
      def test_table_alias_length
        assert_nothing_raised do
          @connection.table_alias_length
        end
      end
181 182

      def test_exec_no_binds
183
        with_example_table do
184
          result = @connection.exec_query("SELECT id, data FROM ex")
185 186 187 188
          assert_equal 0, result.rows.length
          assert_equal 2, result.columns.length
          assert_equal %w{ id data }, result.columns

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

195
          assert_equal [[1, "foo"]], result.rows
196
        end
197
      end
198

199 200 201
      if ActiveRecord::Base.connection.prepared_statements
        def test_exec_with_binds
          with_example_table do
202
            string = @connection.quote("foo")
203
            @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
204

205 206
            binds = [bind_attribute("id", 1)]
            result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, binds)
207

208 209 210
            assert_equal 1, result.rows.length
            assert_equal 2, result.columns.length

211
            assert_equal [[1, "foo"]], result.rows
212
          end
213
        end
A
Aaron Patterson 已提交
214

215 216
        def test_exec_typecasts_bind_vals
          with_example_table do
217
            string = @connection.quote("foo")
218
            @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
A
Aaron Patterson 已提交
219

220 221
            binds = [bind_attribute("id", "1-fuu", Type::Integer.new)]
            result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, binds)
A
Aaron Patterson 已提交
222

223 224
            assert_equal 1, result.rows.length
            assert_equal 2, result.columns.length
A
Aaron Patterson 已提交
225

226
            assert_equal [[1, "foo"]], result.rows
227
          end
228
        end
A
Aaron Patterson 已提交
229
      end
230

231
      def test_partial_index
232
        with_example_table do
233
          @connection.add_index "ex", %w{ id number }, name: "partial", where: "number > 100"
234
          index = @connection.indexes("ex").find { |idx| idx.name == "partial" }
235 236
          assert_equal "(number > 100)", index.where
        end
237 238
      end

239 240
      def test_expression_index
        with_example_table do
241 242 243
          @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
244 245 246 247 248
        end
      end

      def test_index_with_opclass
        with_example_table do
249 250
          @connection.add_index "ex", "data varchar_pattern_ops"
          index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
251
          assert_equal "data varchar_pattern_ops", index.columns
252 253 254

          @connection.remove_index "ex", "data varchar_pattern_ops"
          assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
255 256 257
        end
      end

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
      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

273 274
      def test_columns_for_distinct_with_case
        assert_equal(
275 276
          "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",
277 278 279 280
            ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
        )
      end

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
      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

300 301 302 303 304 305 306 307 308 309 310
      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

311 312 313 314 315 316
      def test_raise_error_when_cannot_translate_exception
        assert_raise TypeError do
          @connection.send(:log, nil) { @connection.execute(nil) }
        end
      end

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
      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

343 344 345 346 347 348
      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"
        }
349
        assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'\. It will be treated as String\.\n\z/, warning)
350 351 352 353
      ensure
        reset_connection
      end

354 355 356
      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
357
            self.table_name = "ex"
358 359 360 361 362 363 364 365 366 367 368 369 370
          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

371
      private
372

373 374 375
        def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block)
          super(@connection, "ex", definition, &block)
        end
376

377 378 379
        def connection_without_insert_returning
          ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations["arunit"].merge(insert_returning: false))
        end
380 381 382
    end
  end
end