diff options
Diffstat (limited to 'activerecord/test/cases/adapters')
35 files changed, 1356 insertions, 581 deletions
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 0878925a6c..7c0f11b033 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -1,23 +1,23 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end end end - def teardown - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + teardown do + reset_connection end def test_add_index - # add_index calls index_name_exists? which can't work since execute is stubbed + # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_indexes_in_create + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end + + assert_equal expected, actual + end + private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb index 97adb6b297..340fc95503 100644 --- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -3,10 +3,10 @@ require 'models/person' class MysqlCaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base - validates_uniqueness_of :string_cs_column, :case_sensitive => false - validates_uniqueness_of :string_ci_column, :case_sensitive => false end + repair_validations(CollationTest) + def test_columns_include_collation_different_from_table assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation @@ -18,6 +18,7 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase end def test_case_insensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'a') queries = assert_sql { invalid.save } @@ -26,10 +27,29 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase end def test_case_insensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'a') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_match(/lower/i, cs_uniqueness_query) end + + def test_case_sensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'A') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_match(/binary/i, ci_uniqueness_query) + end + + def test_case_sensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'A') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } + assert_no_match(/binary/i, cs_uniqueness_query) + end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 5cd5d8ac5f..412efa22ff 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,6 +1,11 @@ require "cases/helper" +require 'support/connection_helper' +require 'support/ddl_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + include DdlHelper + class Klass < ActiveRecord::Base end @@ -69,59 +74,50 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_exec_no_binds - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - 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 + 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 - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - # if there are no bind parameters, it will return a string (due to - # the libmysql api) - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_exec_with_binds - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_exec_typecasts_bind_vals - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @connection.columns('ex').find { |col| col.name == 'id' } + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end # Test that MySQL allows multiple results for stored procedures @@ -166,12 +162,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase private - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end + def with_example_table(&block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `data` varchar(255) + SQL + super(@connection, 'ex', definition, &block) end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 578f6301bd..1699380eb3 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -1,19 +1,15 @@ # encoding: utf-8 require "cases/helper" +require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class MysqlAdapterTest < ActiveRecord::TestCase + include DdlHelper + def setup @conn = ActiveRecord::Base.connection - @conn.exec_query('drop table if exists ex') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex` ( - `id` int(11) auto_increment PRIMARY KEY, - `number` integer, - `data` varchar(255)) - eosql end def test_bad_connection_mysql @@ -25,8 +21,10 @@ module ActiveRecord end def test_valid_column - column = @conn.columns('ex').find { |col| col.name == 'id' } - assert @conn.valid_type?(column.type) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end end def test_invalid_column @@ -38,31 +36,35 @@ module ActiveRecord end def test_exec_insert_number - insert(@conn, 'number' => 10) + with_example_table do + insert(@conn, 'number' => 10) - result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') + result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') - assert_equal 1, result.rows.length - # if there are no bind parameters, it will return a string (due to - # the libmysql api) - assert_equal '10', result.rows.last.last + assert_equal 1, result.rows.length + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + assert_equal '10', result.rows.last.last + end end def test_exec_insert_string - str = 'いただきます!' - insert(@conn, 'number' => 10, 'data' => str) + with_example_table do + str = 'いただきます!' + insert(@conn, 'number' => 10, 'data' => str) - result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') + result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') - value = result.rows.last.last + value = result.rows.last.last - # FIXME: this should probably be inside the mysql AR adapter? - value.force_encoding(@conn.client_encoding) + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) - # The strings in this file are utf-8, so transcode to utf-8 - value.encode!(Encoding::UTF_8) + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) - assert_equal str, value + assert_equal str, value + end end def test_tables_quoting @@ -74,46 +76,37 @@ module ActiveRecord end def test_pk_and_sequence_for - pk, seq = @conn.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @conn.default_sequence_name('ex', 'id'), seq + with_example_table do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end end def test_pk_and_sequence_for_with_non_standard_primary_key - @conn.exec_query('drop table if exists ex_with_non_standard_pk') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_non_standard_pk` ( - `code` INT(11) auto_increment, - PRIMARY KEY (`code`)) - eosql - pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk') - assert_equal 'code', pk - assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq + with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @conn.default_sequence_name('ex', 'code'), seq + end end def test_pk_and_sequence_for_with_custom_index_type_pk - @conn.exec_query('drop table if exists ex_with_custom_index_type_pk') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_custom_index_type_pk` ( - `id` INT(11) auto_increment, - PRIMARY KEY USING BTREE (`id`)) - eosql - pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk') - assert_equal 'id', pk - assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq + with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end end def test_tinyint_integer_typecasting - @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_non_boolean_tinyint_column` ( - `status` TINYINT(4)) - eosql - insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column') + with_example_table '`status` TINYINT(4)' do + insert(@conn, { 'status' => 2 }, 'ex') - result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column') + result = @conn.exec_query('SELECT status FROM ex') - assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + end end def test_supports_extensions @@ -140,6 +133,15 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + + def with_example_table(definition = nil, &block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `number` integer, + `data` varchar(255) + SQL + super(@conn, 'ex', definition, &block) + end end end end diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 8eb9565963..61ae0abfd1 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase 'distinct_select'=>'distinct_id int, select_id int' end - def teardown + teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 4ccf568406..cefc3e3c7e 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,23 +1,23 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end end end - def teardown - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + teardown do + reset_connection end def test_add_index - # add_index calls index_name_exists? which can't work since execute is stubbed + # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).table_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_indexes_in_create + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end + + assert_equal expected, actual + end + private def with_real_execute ActiveRecord::Base.connection.singleton_class.class_eval do diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index 6bcc113482..09bebf3071 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -3,10 +3,10 @@ require 'models/person' class Mysql2CaseSensitivityTest < ActiveRecord::TestCase class CollationTest < ActiveRecord::Base - validates_uniqueness_of :string_cs_column, :case_sensitive => false - validates_uniqueness_of :string_ci_column, :case_sensitive => false end + repair_validations(CollationTest) + def test_columns_include_collation_different_from_table assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation @@ -18,6 +18,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase end def test_case_insensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'a') queries = assert_sql { invalid.save } @@ -26,10 +27,29 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase end def test_case_insensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'a') queries = assert_sql { invalid.save } cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)} assert_match(/lower/i, cs_uniqueness_query) end + + def test_case_sensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'A') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_match(/binary/i, ci_uniqueness_query) + end + + def test_case_sensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'A') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } + assert_no_match(/binary/i, cs_uniqueness_query) + end end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 9b7202c915..182d9409c7 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,15 +1,18 @@ require "cases/helper" +require 'support/connection_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + def setup super @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) + ActiveSupport::Notifications.unsubscribe(@subscription) super end @@ -97,14 +100,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase @connection.execute "DROP TABLE `bar_baz`" end - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) + if mysql_56? + def test_quote_time_usec + assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0)) + assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime) end end end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 1cd356e868..675703caa1 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -9,15 +9,15 @@ module ActiveRecord def test_explain_for_one_query explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain assert_match %r(developers |.* const), explain end def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain assert_match %r(developers |.* const), explain - assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain + assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain assert_match %r(audit_logs |.* ALL), explain end end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 1a82308176..799d927ee4 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase 'distinct_select'=>'distinct_id int, select_id int' end - def teardown + teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 22dd48e113..3808db5141 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -7,7 +7,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase end end - def teardown + teardown do ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do remove_method :execute end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index d71e2aa2bb..18dd4a6de8 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -19,14 +19,17 @@ class PostgresqlArrayTest < ActiveRecord::TestCase @column = PgArray.columns.find { |c| c.name == 'tags' } end - def teardown + teardown do @connection.execute 'drop table if exists pg_arrays' end def test_column assert_equal :string, @column.type + assert_equal "character varying", @column.sql_type assert @column.array assert_not @column.text? + assert_not @column.number? + assert_not @column.binary? ratings_column = PgArray.columns_hash['ratings'] assert_equal :integer, ratings_column.type @@ -34,6 +37,28 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_not ratings_column.number? end + def test_default + @connection.add_column 'pg_arrays', 'score', :integer, array: true, default: [4, 4, 2] + PgArray.reset_column_information + column = PgArray.columns_hash["score"] + + assert_equal([4, 4, 2], column.default) + assert_equal([4, 4, 2], PgArray.new.score) + ensure + PgArray.reset_column_information + end + + def test_default_strings + @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"] + PgArray.reset_column_information + column = PgArray.columns_hash["names"] + + assert_equal(["foo", "bar"], column.default) + assert_equal(["foo", "bar"], PgArray.new.names) + ensure + PgArray.reset_column_information + end + def test_change_column_with_array @connection.add_column :pg_arrays, :snippets, :string, array: true, default: [] @connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}" @@ -93,6 +118,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) end + def test_with_empty_strings + assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) + end + + def test_with_multi_dimensional_empty_strings + assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) + end + + def test_with_arbitrary_whitespace + assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) + end + def test_multi_dimensional_with_integers assert_cycle(:ratings, [[[1], [7]], [[8], [10]]]) end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index b8dd35c4c5..e3478856c8 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -23,7 +23,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)) end - def teardown + teardown do @connection.execute 'drop table if exists bytea_data_type' end @@ -70,6 +70,23 @@ class PostgresqlByteaTest < ActiveRecord::TestCase assert_equal(data, record.payload) end + def test_via_to_sql + data = "'\u001F\\" + ByteaDataType.create(payload: data) + sql = ByteaDataType.where(payload: data).select(:payload).to_sql + result = @connection.query(sql) + assert_equal([[data]], result) + end + + def test_via_to_sql_with_complicating_connection + Thread.new do + other_conn = ActiveRecord::Base.connection + other_conn.execute('SET standard_conforming_strings = off') + end.join + + test_via_to_sql + end + def test_write_binary data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb new file mode 100644 index 0000000000..948bf49a54 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -0,0 +1,80 @@ +# encoding: utf-8 + +require 'cases/helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +if ActiveRecord::Base.connection.supports_extensions? + class PostgresqlCitextTest < ActiveRecord::TestCase + class Citext < ActiveRecord::Base + self.table_name = 'citexts' + end + + def setup + @connection = ActiveRecord::Base.connection + + unless @connection.extension_enabled?('citext') + @connection.enable_extension 'citext' + @connection.commit_db_transaction + end + + @connection.reconnect! + + @connection.create_table('citexts') do |t| + t.citext 'cival' + end + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS citexts;' + @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;' + end + + def test_citext_enabled + assert @connection.extension_enabled?('citext') + end + + def test_column + column = Citext.columns_hash['cival'] + assert_equal :citext, column.type + assert_equal 'citext', column.sql_type + assert_not column.text? + assert_not column.number? + assert_not column.binary? + assert_not column.array + end + + def test_change_table_supports_json + @connection.transaction do + @connection.change_table('citexts') do |t| + t.citext 'username' + end + Citext.reset_column_information + column = Citext.columns.find { |c| c.name == 'username' } + assert_equal :citext, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Citext.reset_column_information + end + + def test_write + x = Citext.new(cival: 'Some CI Text') + x.save! + citext = Citext.first + assert_equal "Some CI Text", citext.cival + + citext.cival = "Some NEW CI Text" + citext.save! + + assert_equal "Some NEW CI Text", citext.reload.cival + end + + def test_select_case_insensitive + @connection.execute "insert into citexts (cival) values('Cased Text')" + x = Citext.where(cival: 'cased text').first + assert_equal 'Cased Text', x.cival + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb new file mode 100644 index 0000000000..224b1b770b --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlCompositeTest < ActiveRecord::TestCase + class PostgresqlComposite < ActiveRecord::Base + self.table_name = "postgresql_composites" + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_composites' + @connection.execute 'DROP TYPE IF EXISTS full_address' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute <<-SQL + CREATE TYPE full_address AS + ( + city VARCHAR(90), + street VARCHAR(90) + ); + SQL + @connection.create_table('postgresql_composites') do |t| + t.column :address, :full_address + end + end + end + + def test_column + column = PostgresqlComposite.columns_hash["address"] + # TODO: Composite columns should have a type + assert_nil column.type + assert_equal "full_address", column.sql_type + assert_not column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_composite_mapping + @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));" + composite = PostgresqlComposite.first + assert_equal "(Paris,Champs-Élysées)", composite.address + + composite.address = "(Paris,Rue Basse)" + composite.save! + + assert_equal '(Paris,"Rue Basse")', composite.reload.address + end +end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 90cca7d3e6..5f84c893c0 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -1,19 +1,22 @@ require "cases/helper" +require 'support/connection_helper' module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + class NonExistentTable < ActiveRecord::Base end def setup super @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) @connection = ActiveRecord::Base.connection end def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) + ActiveSupport::Notifications.unsubscribe(@subscription) super end @@ -45,6 +48,37 @@ module ActiveRecord assert_equal 'off', expect end + def test_reset + @connection.query('ROLLBACK') + @connection.query('SET geqo TO off') + + # Verify the setting has been applied. + expect = @connection.query('show geqo').first.first + assert_equal 'off', expect + + @connection.reset! + + # Verify the setting has been cleared. + expect = @connection.query('show geqo').first.first + assert_equal 'on', expect + end + + def test_reset_with_transaction + @connection.query('ROLLBACK') + @connection.query('SET geqo TO off') + + # Verify the setting has been applied. + expect = @connection.query('show geqo').first.first + assert_equal 'off', expect + + @connection.query('BEGIN') + @connection.reset! + + # Verify the setting has been cleared. + expect = @connection.query('show geqo').first.first + assert_equal 'on', expect + end + def test_tables_logs_name @connection.tables('hello') assert_equal 'SCHEMA', @subscriber.logged[0][1] @@ -91,40 +125,50 @@ module ActiveRecord assert_operator plan.length, :>, 0 end - # Must have with_manual_interventions set to true for this - # test to run. + # Must have PostgreSQL >= 9.2, or with_manual_interventions set to + # true for this test to run. + # # When prompted, restart the PostgreSQL server with the # "-m fast" option or kill the individual connection assuming # you know the incantation to do that. # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" - if ARTest.config['with_manual_interventions'] - def test_reconnection_after_actual_disconnection_with_verify - original_connection_pid = @connection.query('select pg_backend_pid()') + def test_reconnection_after_actual_disconnection_with_verify + original_connection_pid = @connection.query('select pg_backend_pid()') - # Sanity check. - assert @connection.active? + # Sanity check. + assert @connection.active? + if @connection.send(:postgresql_version) >= 90200 + secondary_connection = ActiveRecord::Base.connection_pool.checkout + secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})") + ActiveRecord::Base.connection_pool.checkin(secondary_connection) + elsif ARTest.config['with_manual_interventions'] puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + 'server with the "-m fast" option) and then press enter.' $stdin.gets + else + # We're not capable of terminating the backend ourselves, and + # we're not allowed to seek assistance; bail out without + # actually testing anything. + return + end - @connection.verify! + @connection.verify! - assert @connection.active? + assert @connection.active? - # If we get no exception here, then either we re-connected successfully, or - # we never actually got disconnected. - new_connection_pid = @connection.query('select pg_backend_pid()') + # If we get no exception here, then either we re-connected successfully, or + # we never actually got disconnected. + new_connection_pid = @connection.query('select pg_backend_pid()') - assert_not_equal original_connection_pid, new_connection_pid, - "umm -- looks like you didn't break the connection, because we're still " + - "successfully querying with the same connection pid." + assert_not_equal original_connection_pid, new_connection_pid, + "umm -- looks like you didn't break the connection, because we're still " + + "successfully querying with the same connection pid." - # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! end end @@ -157,17 +201,5 @@ module ActiveRecord ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) end end - - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end - end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 04a458fbce..e7dda1a1af 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -27,9 +27,6 @@ end class PostgresqlTimestampWithZone < ActiveRecord::Base end -class PostgresqlUUID < ActiveRecord::Base -end - class PostgresqlLtree < ActiveRecord::Base end @@ -68,14 +65,19 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @first_oid = PostgresqlOid.find(1) @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") - - @connection.execute("INSERT INTO postgresql_uuids (id, guid, compact_guid) VALUES(1, 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')") - @first_uuid = PostgresqlUUID.find(1) end - def teardown + teardown do [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress, - PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all) + PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all) + end + + def test_array_escaping + unknown = %(foo\\",bar,baz,\\) + nicknames = ["hello_#{unknown}"] + ar = PostgresqlArray.create!(nicknames: nicknames, id: 100) + ar.reload + assert_equal nicknames, ar.nicknames end def test_data_type_of_array_types @@ -116,10 +118,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type end - def test_data_type_of_uuid_types - assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type - end - def test_array_values assert_equal [35000,21000,18000,17000], @first_array.commission_by_quarter assert_equal ['foo','bar','baz'], @first_array.nicknames @@ -172,11 +170,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address end - def test_uuid_values - assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid - assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid - end - def test_bit_string_values assert_equal '00010101', @first_bit_string.bit_string assert_equal '00010101', @first_bit_string.bit_string_varying diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb new file mode 100644 index 0000000000..5286a847a4 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'support/connection_helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlDomainTest < ActiveRecord::TestCase + include ConnectionHelper + + class PostgresqlDomain < ActiveRecord::Base + self.table_name = "postgresql_domains" + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)" + @connection.create_table('postgresql_domains') do |t| + t.column :price, :custom_money + end + end + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_domains' + @connection.execute 'DROP DOMAIN IF EXISTS custom_money' + reset_connection + end + + def test_column + column = PostgresqlDomain.columns_hash["price"] + assert_equal :decimal, column.type + assert_equal "custom_money", column.sql_type + assert column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_domain_acts_like_basetype + PostgresqlDomain.create price: "" + record = PostgresqlDomain.first + assert_nil record.price + + record.price = "34.15" + record.save! + + assert_equal BigDecimal.new("34.15"), record.reload.price + end +end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb new file mode 100644 index 0000000000..4146b117f6 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'support/connection_helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlEnumTest < ActiveRecord::TestCase + include ConnectionHelper + + class PostgresqlEnum < ActiveRecord::Base + self.table_name = "postgresql_enums" + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute <<-SQL + CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); + SQL + @connection.create_table('postgresql_enums') do |t| + t.column :current_mood, :mood + end + end + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' + @connection.execute 'DROP TYPE IF EXISTS mood' + reset_connection + end + + def test_column + column = PostgresqlEnum.columns_hash["current_mood"] + assert_equal :enum, column.type + assert_equal "mood", column.sql_type + assert_not column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_enum_mapping + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + enum = PostgresqlEnum.first + assert_equal "sad", enum.current_mood + + enum.current_mood = "happy" + enum.save! + + assert_equal "happy", enum.reload.current_mood + end + + def test_invalid_enum_update + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + enum = PostgresqlEnum.first + enum.current_mood = "angry" + + assert_raise ActiveRecord::StatementInvalid do + enum.save + end + end + + def test_no_oid_warning + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + stderr_output = capture(:stderr) { PostgresqlEnum.first } + + assert stderr_output.blank? + end + + def test_enum_type_cast + enum = PostgresqlEnum.new + enum.current_mood = :happy + + assert_equal "happy", enum.current_mood + end +end diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 0b61f61572..416f84cb38 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -9,7 +9,7 @@ module ActiveRecord def test_explain_for_one_query explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain assert_match %(QUERY PLAN), explain assert_match %(Index Scan using developers_pkey on developers), explain end @@ -17,9 +17,9 @@ module ActiveRecord def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(QUERY PLAN), explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain assert_match %(Index Scan using developers_pkey on developers), explain - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match %(Seq Scan on audit_logs), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index d8782f5eaa..c24c4b0d56 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -24,13 +24,14 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @connection.transaction do @connection.create_table('hstores') do |t| t.hstore 'tags', :default => '' + t.hstore 'payload', array: true t.hstore 'settings' end end @column = Hstore.columns.find { |c| c.name == 'tags' } end - def teardown + teardown do @connection.execute 'drop table if exists hstores' end @@ -53,6 +54,22 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_column assert_equal :hstore, @column.type + assert_equal "hstore", @column.sql_type + assert_not @column.number? + assert_not @column.text? + assert_not @column.binary? + assert_not @column.array + end + + def test_default + @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"' + Hstore.reset_column_information + column = Hstore.columns_hash["permissions"] + + assert_equal({"users"=>"read", "articles"=>"write"}, column.default) + assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions) + ensure + Hstore.reset_column_information end def test_change_table_supports_hstore @@ -182,6 +199,30 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal({'1' => '2'}, x.tags) end + def test_array_cycle + assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}]) + end + + def test_array_strings_with_quotes + assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}]) + end + + def test_array_strings_with_commas + assert_array_cycle([{'this,has' => 'many,values'}]) + end + + def test_array_strings_with_array_delimiters + assert_array_cycle(['{' => '}']) + end + + def test_array_strings_with_null_strings + assert_array_cycle([{'NULL' => 'NULL'}]) + end + + def test_contains_nils + assert_array_cycle([{'NULL' => nil}]) + end + def test_select_multikey @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" x = Hstore.first @@ -237,6 +278,20 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase private + def assert_array_cycle(array) + # test creation + x = Hstore.create!(payload: array) + x.reload + assert_equal(array, x.payload) + + # test updating + x = Hstore.create!(payload: []) + x.payload = array + x.save! + x.reload + assert_equal(array, x.payload) + end + def assert_cycle(hash) # test creation x = Hstore.create!(:tags => hash) diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 01e7334aad..ee793ffff2 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -21,17 +21,34 @@ class PostgresqlJSONTest < ActiveRecord::TestCase end end rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without json" + skip "do not test on PG without json" end @column = JsonDataType.columns.find { |c| c.name == 'payload' } end - def teardown + teardown do @connection.execute 'drop table if exists json_data_type' end def test_column - assert_equal :json, @column.type + column = JsonDataType.columns_hash["payload"] + assert_equal :json, column.type + assert_equal "json", column.sql_type + assert_not column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_default + @connection.add_column 'json_data_type', 'permissions', :json, default: '{"users": "read", "posts": ["read", "write"]}' + JsonDataType.reset_column_information + column = JsonDataType.columns_hash["permissions"] + + assert_equal({"users"=>"read", "posts"=>["read", "write"]}, column.default) + assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions) + ensure + JsonDataType.reset_column_information end def test_change_table_supports_json @@ -57,16 +74,16 @@ class PostgresqlJSONTest < ActiveRecord::TestCase end def test_type_cast_json - assert @column + column = JsonDataType.columns_hash["payload"] data = "{\"a_key\":\"a_value\"}" - hash = @column.class.string_to_json data + hash = column.class.string_to_json data assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, @column.type_cast(data)) + assert_equal({'a_key' => 'a_value'}, column.type_cast(data)) - assert_equal({}, @column.type_cast("{}")) - assert_equal({'key'=>nil}, @column.type_cast('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"}))) + assert_equal({}, column.type_cast("{}")) + assert_equal({'key'=>nil}, column.type_cast('{"key": null}')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 5d12ca75ca..718f37a380 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -10,6 +10,11 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection + + unless @connection.extension_enabled?('ltree') + @connection.enable_extension 'ltree' + end + @connection.transaction do @connection.create_table('ltrees') do |t| t.ltree 'path' @@ -19,13 +24,18 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase skip "do not test on PG without ltree" end - def teardown + teardown do @connection.execute 'drop table if exists ltrees' end def test_column column = Ltree.columns_hash['path'] assert_equal :ltree, column.type + assert_equal "ltree", column.sql_type + assert_not column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array end def test_write diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 131080913c..b7791078db 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,26 +1,31 @@ # encoding: utf-8 require "cases/helper" +require 'support/ddl_helper' +require 'support/connection_helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::TestCase + include DdlHelper + include ConnectionHelper + def setup @connection = ActiveRecord::Base.connection - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db') connection = ActiveRecord::Base.postgresql_connection(configuration) - connection.exec_query('drop table if exists ex') + connection.exec_query('SELECT 1') end end def test_valid_column - column = @connection.columns('ex').find { |col| col.name == 'id' } - assert @connection.valid_type?(column.type) + with_example_table do + column = @connection.columns('ex').find { |col| col.name == 'id' } + assert @connection.valid_type?(column.type) + end end def test_invalid_column @@ -28,7 +33,9 @@ module ActiveRecord end def test_primary_key - assert_equal 'id', @connection.primary_key('ex') + with_example_table do + assert_equal 'id', @connection.primary_key('ex') + end end def test_primary_key_works_tables_containing_capital_letters @@ -36,15 +43,15 @@ module ActiveRecord end def test_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(data character varying(255) primary key)') - assert_equal 'data', @connection.primary_key('ex') + with_example_table 'data character varying(255) primary key' do + assert_equal 'data', @connection.primary_key('ex') + end end def test_primary_key_returns_nil_for_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.primary_key('ex') + with_example_table 'id integer' do + assert_nil @connection.primary_key('ex') + end end def test_primary_key_raises_error_if_table_not_found @@ -54,32 +61,40 @@ module ActiveRecord end def test_insert_sql_with_proprietary_returning_clause - id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") - assert_equal "5150", id + with_example_table do + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end end def test_insert_sql_with_quoted_schema_and_table_name - 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 + 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 end def test_insert_sql_with_no_space_after_table_name - id = @connection.insert_sql("insert into ex(number) values(5150)") - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + 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 end def test_multiline_insert_sql - 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 + 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 end def test_insert_sql_with_returning_disabled @@ -135,53 +150,104 @@ module ActiveRecord end def test_pk_and_sequence_for - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @connection.default_sequence_name('ex', 'id'), seq + with_example_table do + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end end def test_pk_and_sequence_for_with_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(code serial primary key)') - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'code', pk - assert_equal @connection.default_sequence_name('ex', 'code'), seq + 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 + end end def test_pk_and_sequence_for_returns_nil_if_no_seq - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer primary key)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer primary key' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_table_not_found assert_nil @connection.pk_and_sequence_for('unobtainium') end + 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 + assert_equal 'ex_id_seq', seq + + @connection.exec_query( + "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" + ) + ensure + @connection.exec_query('DROP TABLE IF EXISTS ex') + @connection.exec_query('DROP TABLE IF EXISTS ex2') + end + def test_exec_insert_number - insert(@connection, 'number' => 10) + with_example_table do + insert(@connection, 'number' => 10) - result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') - assert_equal 1, result.rows.length - assert_equal "10", result.rows.last.last + assert_equal 1, result.rows.length + assert_equal "10", result.rows.last.last + end end def test_exec_insert_string - str = 'いただきます!' - insert(@connection, 'number' => 10, 'data' => str) + with_example_table do + str = 'いただきます!' + insert(@connection, 'number' => 10, 'data' => str) - result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') - value = result.rows.last.last + value = result.rows.last.last - assert_equal str, value + assert_equal str, value + end end def test_table_alias_length @@ -191,44 +257,50 @@ module ActiveRecord end def test_exec_no_binds - 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 - - assert_equal [['1', 'foo']], result.rows + 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 + + assert_equal [['1', 'foo']], result.rows + end end def test_exec_with_binds - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_exec_typecasts_bind_vals - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) + column = @connection.columns('ex').find { |col| col.name == 'id' } + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_substitute_at @@ -240,9 +312,11 @@ module ActiveRecord end def test_partial_index - @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 + 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 end def test_columns_for_distinct_zero_orders @@ -285,6 +359,43 @@ module ActiveRecord end end + 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 + + 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 + private def insert(ctx, data) binds = data.map { |name, value| @@ -300,6 +411,10 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block) + super(@connection, 'ex', definition, &block) + end + def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 1122f8b9a1..51846e22d9 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -10,46 +10,46 @@ module ActiveRecord end def test_type_cast_true - c = Column.new(nil, 1, 'boolean') + c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean') assert_equal 't', @conn.type_cast(true, nil) assert_equal 't', @conn.type_cast(true, c) end def test_type_cast_false - c = Column.new(nil, 1, 'boolean') + c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean') assert_equal 'f', @conn.type_cast(false, nil) assert_equal 'f', @conn.type_cast(false, c) end def test_type_cast_cidr ip = IPAddr.new('255.0.0.0/8') - c = Column.new(nil, ip, 'cidr') + c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr') assert_equal ip, @conn.type_cast(ip, c) end def test_type_cast_inet ip = IPAddr.new('255.1.0.0/8') - c = Column.new(nil, ip, 'inet') + c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet') assert_equal ip, @conn.type_cast(ip, c) end def test_quote_float_nan nan = 0.0/0 - c = Column.new(nil, 1, 'float') + c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') assert_equal "'NaN'", @conn.quote(nan, c) end def test_quote_float_infinity infinity = 1.0/0 - c = Column.new(nil, 1, 'float') + c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float') assert_equal "'Infinity'", @conn.quote(infinity, c) end def test_quote_cast_numeric fixnum = 666 - c = Column.new(nil, nil, 'varchar') + c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar') assert_equal "'666'", @conn.quote(fixnum, c) - c = Column.new(nil, nil, 'text') + c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index a56b8ac791..060b17d071 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'support/connection_helper' require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' @@ -8,14 +9,20 @@ if ActiveRecord::Base.connection.supports_ranges? end class PostgresqlRangeTest < ActiveRecord::TestCase - def teardown - @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' - end + self.use_transactional_fixtures = false + include ConnectionHelper def setup - @connection = ActiveRecord::Base.connection + @connection = PostgresqlRange.connection begin @connection.transaction do + @connection.execute <<_SQL + CREATE TYPE floatrange AS RANGE ( + subtype = float8, + subtype_diff = float8mi + ); +_SQL + @connection.create_table('postgresql_ranges') do |t| t.daterange :date_range t.numrange :num_range @@ -24,9 +31,12 @@ if ActiveRecord::Base.connection.supports_ranges? t.int4range :int4_range t.int8range :int8_range end + + @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' end + PostgresqlRange.reset_column_information rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without range" + skip "do not test on PG without range" end insert_range(id: 101, @@ -35,23 +45,26 @@ if ActiveRecord::Base.connection.supports_ranges? ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']", tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']", int4_range: "[1, 10]", - int8_range: "[10, 100]") + int8_range: "[10, 100]", + float_range: "[0.5, 0.7]") insert_range(id: 102, - date_range: "(''2012-01-02'', ''2012-01-04'')", + date_range: "[''2012-01-02'', ''2012-01-04'')", num_range: "[0.1, 0.2)", ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')", tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')", - int4_range: "(1, 10)", - int8_range: "(10, 100)") + int4_range: "[1, 10)", + int8_range: "[10, 100)", + float_range: "[0.5, 0.7)") insert_range(id: 103, - date_range: "(''2012-01-02'',]", + date_range: "[''2012-01-02'',]", num_range: "[0.1,]", ts_range: "[''2010-01-01 14:30'',]", tstz_range: "[''2010-01-01 14:30:00+05'',]", - int4_range: "(1,]", - int8_range: "(10,]") + int4_range: "[1,]", + int8_range: "[10,]", + float_range: "[0.5,]") insert_range(id: 104, date_range: "[,]", @@ -59,15 +72,17 @@ if ActiveRecord::Base.connection.supports_ranges? ts_range: "[,]", tstz_range: "[,]", int4_range: "[,]", - int8_range: "[,]") + int8_range: "[,]", + float_range: "[,]") insert_range(id: 105, - date_range: "(''2012-01-02'', ''2012-01-02'')", - num_range: "(0.1, 0.1)", - ts_range: "(''2010-01-01 14:30'', ''2010-01-01 14:30'')", - tstz_range: "(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')", - int4_range: "(1, 1)", - int8_range: "(10, 10)") + date_range: "[''2012-01-02'', ''2012-01-02'')", + num_range: "[0.1, 0.1)", + ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')", + tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')", + int4_range: "[1, 1)", + int8_range: "[10, 10)", + float_range: "[0.5, 0.5)") @new_range = PostgresqlRange.new @first_range = PostgresqlRange.find(101) @@ -77,6 +92,12 @@ if ActiveRecord::Base.connection.supports_ranges? @empty_range = PostgresqlRange.find(105) end + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' + @connection.execute 'DROP TYPE IF EXISTS floatrange' + reset_connection + end + def test_data_type_of_range_types assert_equal :daterange, @first_range.column_for_attribute(:date_range).type assert_equal :numrange, @first_range.column_for_attribute(:num_range).type @@ -88,24 +109,24 @@ if ActiveRecord::Base.connection.supports_ranges? def test_int4range_values assert_equal 1...11, @first_range.int4_range - assert_equal 2...10, @second_range.int4_range - assert_equal 2...Float::INFINITY, @third_range.int4_range + assert_equal 1...10, @second_range.int4_range + assert_equal 1...Float::INFINITY, @third_range.int4_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) assert_nil @empty_range.int4_range end def test_int8range_values assert_equal 10...101, @first_range.int8_range - assert_equal 11...100, @second_range.int8_range - assert_equal 11...Float::INFINITY, @third_range.int8_range + assert_equal 10...100, @second_range.int8_range + assert_equal 10...Float::INFINITY, @third_range.int8_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) assert_nil @empty_range.int8_range end def test_daterange_values assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range - assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range - assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range + assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) assert_nil @empty_range.date_range end @@ -133,6 +154,14 @@ if ActiveRecord::Base.connection.supports_ranges? assert_nil @empty_range.tstz_range end + def test_custom_range_values + assert_equal 0.5..0.7, @first_range.float_range + assert_equal 0.5...0.7, @second_range.float_range + assert_equal 0.5...Float::INFINITY, @third_range.float_range + assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range + assert_nil @empty_range.float_range + end + def test_create_tstzrange tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') round_trip(@new_range, :tstz_range, tstzrange) @@ -203,6 +232,38 @@ if ActiveRecord::Base.connection.supports_ranges? assert_nil_round_trip(@first_range, :int8_range, 39999...39999) end + def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated + tz = ::ActiveRecord::Base.default_timezone + + silence_warnings { + assert_deprecated { + range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") + assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range + } + assert_deprecated { + range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range + } + assert_deprecated { + range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") + assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range + } + assert_deprecated { + range = PostgresqlRange.create!(int4_range: "(1, 10]") + assert_equal 2..10, range.int4_range + } + assert_deprecated { + range = PostgresqlRange.create!(int8_range: "(10, 100]") + assert_equal 11..100, range.int8_range + } + } + end + + def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported + assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") } + end + private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) @@ -229,7 +290,8 @@ if ActiveRecord::Base.connection.supports_ranges? ts_range, tstz_range, int4_range, - int8_range + int8_range, + float_range ) VALUES ( #{values[:id]}, '#{values[:date_range]}', @@ -237,7 +299,8 @@ if ActiveRecord::Base.connection.supports_ranges? '#{values[:ts_range]}', '#{values[:tstz_range]}', '#{values[:int4_range]}', - '#{values[:int8_range]}' + '#{values[:int8_range]}', + '#{values[:float_range]}' ) SQL end diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index d5e1838543..99c26c4bf7 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -27,7 +27,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase end end - def teardown + teardown do set_session_auth @connection.execute "RESET search_path" USERS.each do |u| diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index e8dd188ec8..11ec7599a3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -71,7 +71,7 @@ class SchemaTest < ActiveRecord::TestCase @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))" end - def teardown + teardown do @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE" @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end @@ -115,6 +115,12 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_raise_wraped_exception_on_bad_prepare + assert_raises(ActiveRecord::StatementInvalid) do + @connection.exec_query "select * from developers where id = ?", 'sql', [[nil, 1]] + end + end + def test_schema_change_with_prepared_stmt altered = false @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] @@ -240,6 +246,18 @@ class SchemaTest < ActiveRecord::TestCase assert_nothing_raised { with_schema_search_path nil } end + 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 + def test_dump_indexes_for_schema_one do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end @@ -253,13 +271,13 @@ class SchemaTest < ActiveRecord::TestCase end def test_with_uppercase_index_name - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" + assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"} - ActiveRecord::Base.connection.schema_search_path = "public" + with_schema_search_path SCHEMA_NAME do + assert_nothing_raised { @connection.remove_index! "things", "things_Index"} + end end def test_primary_key_with_schema_specified @@ -310,18 +328,17 @@ class SchemaTest < ActiveRecord::TestCase end def test_prepared_statements_with_multiple_schemas + [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 - @connection.schema_search_path = SCHEMA_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA2_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA_NAME - assert_equal 1, Thing5.count - - @connection.schema_search_path = SCHEMA2_NAME - assert_equal 1, Thing5.count + [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| + with_schema_search_path schema_name do + assert_equal 1, Thing5.count + end + end end def test_schema_exists? diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 89210866f0..4d29a20e66 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -77,7 +77,7 @@ class TimestampTest < ActiveRecord::TestCase end def test_bc_timestamp - date = Date.new(0) - 1.second + date = Date.new(0) - 1.week Developer.create!(:name => "aaron", :updated_at => date) assert_equal date, Developer.find_by_name("aaron").updated_at end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 3f5d981444..bdf8e15e3e 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -4,31 +4,101 @@ require "cases/helper" require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' +module PostgresqlUUIDHelper + def connection + @connection ||= ActiveRecord::Base.connection + end + + def enable_uuid_ossp + unless connection.extension_enabled?('uuid-ossp') + connection.enable_extension 'uuid-ossp' + connection.commit_db_transaction + end + + connection.reconnect! + end + + def drop_table(name) + connection.execute "drop table if exists #{name}" + end +end + class PostgresqlUUIDTest < ActiveRecord::TestCase - class UUID < ActiveRecord::Base - self.table_name = 'pg_uuids' + include PostgresqlUUIDHelper + + class UUIDType < ActiveRecord::Base + self.table_name = "uuid_data_type" + end + + setup do + connection.create_table "uuid_data_type" do |t| + t.uuid 'guid' + end end - def setup - @connection = ActiveRecord::Base.connection + teardown do + drop_table "uuid_data_type" + end + + def test_change_column_default + @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()" + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v1()", column.default_function + + @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()" + + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v4()", column.default_function + end + + def test_data_type_of_uuid_types + column = UUIDType.columns_hash["guid"] + assert_equal :uuid, column.type + assert_equal "uuid", column.sql_type + assert_not column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction + def test_treat_blank_uuid_as_nil + UUIDType.create! guid: '' + assert_equal(nil, UUIDType.last.guid) + end + + def test_uuid_formats + ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", + "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", + "a0eebc999c0b4ef8bb6d6bb9bd380a11", + "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11", + "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |valid_uuid| + UUIDType.create(guid: valid_uuid) + uuid = UUIDType.last + assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid end + end +end - @connection.reconnect! +class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase + include PostgresqlUUIDHelper - @connection.transaction do - @connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| - t.string 'name' - t.uuid 'other_uuid', default: 'uuid_generate_v4()' - end + class UUID < ActiveRecord::Base + self.table_name = 'pg_uuids' + end + + setup do + enable_uuid_ossp + + connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| + t.string 'name' + t.uuid 'other_uuid', default: 'uuid_generate_v4()' end end - def teardown - @connection.execute 'drop table if exists pg_uuids' + teardown do + drop_table "pg_uuids" end if ActiveRecord::Base.connection.supports_extensions? @@ -49,14 +119,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end def test_pk_and_sequence_for_uuid_primary_key - pk, seq = @connection.pk_and_sequence_for('pg_uuids') + pk, seq = connection.pk_and_sequence_for('pg_uuids') assert_equal 'id', pk assert_equal nil, seq end def test_schema_dumper_for_uuid_primary_key schema = StringIO.new - ActiveRecord::SchemaDumper.dump(@connection, schema) + ActiveRecord::SchemaDumper.dump(connection, schema) assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string) assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string) end @@ -64,34 +134,24 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase - class UUID < ActiveRecord::Base - self.table_name = 'pg_uuids' - end + include PostgresqlUUIDHelper - def setup - @connection = ActiveRecord::Base.connection - @connection.reconnect! + setup do + enable_uuid_ossp - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction - end - - @connection.transaction do - @connection.create_table('pg_uuids', id: false) do |t| - t.primary_key :id, :uuid, default: nil - t.string 'name' - end + connection.create_table('pg_uuids', id: false) do |t| + t.primary_key :id, :uuid, default: nil + t.string 'name' end end - def teardown - @connection.execute 'drop table if exists pg_uuids' + teardown do + drop_table "pg_uuids" end if ActiveRecord::Base.connection.supports_extensions? def test_id_allows_default_override_via_nil - col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first @@ -101,6 +161,8 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase end class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase + include PostgresqlUUIDHelper + class UuidPost < ActiveRecord::Base self.table_name = 'pg_uuid_posts' has_many :uuid_comments, inverse_of: :uuid_post @@ -111,30 +173,24 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase belongs_to :uuid_post end - def setup - @connection = ActiveRecord::Base.connection - @connection.reconnect! - - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction - end + setup do + enable_uuid_ossp - @connection.transaction do - @connection.create_table('pg_uuid_posts', id: :uuid) do |t| + connection.transaction do + connection.create_table('pg_uuid_posts', id: :uuid) do |t| t.string 'title' end - @connection.create_table('pg_uuid_comments', id: :uuid) do |t| + connection.create_table('pg_uuid_comments', id: :uuid) do |t| t.uuid :uuid_post_id, default: 'uuid_generate_v4()' t.string 'content' end end end - def teardown - @connection.transaction do - @connection.execute 'drop table if exists pg_uuid_comments' - @connection.execute 'drop table if exists pg_uuid_posts' + teardown do + connection.transaction do + drop_table "pg_uuid_comments" + drop_table "pg_uuid_posts" end end diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 66e07b71a0..47b7d38eda 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -1,11 +1,15 @@ require "cases/helper" -class ViewTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false +module ViewTestConcern + extend ActiveSupport::Concern + + included do + self.use_transactional_fixtures = false + mattr_accessor :view_type + end SCHEMA_NAME = 'test_schema' TABLE_NAME = 'things' - VIEW_NAME = 'view_things' COLUMNS = [ 'id integer', 'name character varying(50)', @@ -14,17 +18,19 @@ class ViewTest < ActiveRecord::TestCase ] class ThingView < ActiveRecord::Base - self.table_name = 'test_schema.view_things' end def setup + super + ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things" + @connection = ActiveRecord::Base.connection @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" - @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" - @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}" end def teardown + super @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end @@ -35,7 +41,7 @@ class ViewTest < ActiveRecord::TestCase def test_column_definitions assert_nothing_raised do - assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + assert_equal COLUMNS, columns(ThingView.table_name) end end @@ -47,3 +53,15 @@ class ViewTest < ActiveRecord::TestCase end end + +class ViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'view' +end + +if ActiveRecord::Base.connection.supports_materialized_views? + class MaterializedViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'materialized_view' + end +end diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index bf14b378d8..ae299697b1 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -18,12 +18,12 @@ class PostgresqlXMLTest < ActiveRecord::TestCase end end rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without xml" + skip "do not test on PG without xml" end @column = XmlDataType.columns.find { |c| c.name == 'payload' } end - def teardown + teardown do @connection.execute 'drop table if exists xml_data_type' end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index e78cb88562..b478db749d 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :customers, :companies, :comments, :binaries + fixtures :customers def setup @connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index b227bce680..f1d6119d2e 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -9,15 +9,15 @@ module ActiveRecord def test_explain_for_one_query explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) end def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match(/(SCAN )?TABLE audit_logs/, explain) end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 1a5c3e7aba..e55525177f 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -2,28 +2,22 @@ require "cases/helper" require 'models/owner' require 'tempfile' +require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class SQLite3AdapterTest < ActiveRecord::TestCase + include DdlHelper + self.use_transactional_fixtures = false class DualEncoding < ActiveRecord::Base end def setup - @conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 - @conn.execute <<-eosql - CREATE TABLE items ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer - ) - eosql - - @subscriber = SQLSubscriber.new - ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) + @conn = Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: 100 end def test_bad_connection @@ -33,12 +27,11 @@ module ActiveRecord end end - def test_connect_with_url - skip "can't establish new connection when using memory db" if in_memory_db? - begin + unless in_memory_db? + def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection tf = Tempfile.open 'whatever' - url = "sqlite3://#{tf.path}" + url = "sqlite3:#{tf.path}" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure @@ -46,13 +39,10 @@ module ActiveRecord tf.unlink ActiveRecord::Base.establish_connection(original_connection) end - end - def test_connect_memory_with_url - skip "can't establish new connection when using memory db" if in_memory_db? - begin + def test_connect_memory_with_url original_connection = ActiveRecord::Base.remove_connection - url = "sqlite3:///:memory:" + url = "sqlite3::memory:" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure @@ -61,8 +51,10 @@ module ActiveRecord end def test_valid_column - column = @conn.columns('items').find { |col| col.name == 'id' } - assert @conn.valid_type?(column.type) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end end # sqlite databases should be able to support any type and not @@ -73,13 +65,8 @@ module ActiveRecord assert @conn.valid_type?(:foobar) end - def teardown - ActiveSupport::Notifications.unsubscribe(@subscriber) - super - end - def test_column_types - owner = Owner.create!(:name => "hello".encode('ascii-8bit')) + owner = Owner.create!(name: "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' result = Owner.connection.exec_query <<-esql @@ -89,23 +76,28 @@ module ActiveRecord esql assert(!result.rows.first.include?("blob"), "should not store blobs") + ensure + owner.delete end def test_exec_insert - column = @conn.columns('items').find { |col| col.name == 'number' } - vals = [[column, 10]] - @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'number' } + vals = [[column, 10]] + @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) - result = @conn.exec_query( - 'select number from items where number = ?', 'SQL', vals) + result = @conn.exec_query( + 'select number from ex where number = ?', 'SQL', vals) - assert_equal 1, result.rows.length - assert_equal 10, result.rows.first.first + assert_equal 1, result.rows.length + assert_equal 10, result.rows.first.first + end end def test_primary_key_returns_nil_for_no_pk - @conn.exec_query('create table ex(id int, data string)') - assert_nil @conn.primary_key('ex') + with_example_table 'id int, data string' do + assert_nil @conn.primary_key('ex') + end end def test_connection_no_db @@ -116,17 +108,17 @@ module ActiveRecord def test_bad_timeout assert_raises(TypeError) do - Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 'usa' + Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: 'usa' end end # connection is OK with a nil timeout def test_nil_timeout - conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => nil + conn = Base.sqlite3_connection database: ':memory:', + adapter: 'sqlite3', + timeout: nil assert conn, 'made a connection' end @@ -145,77 +137,83 @@ module ActiveRecord end def test_exec_no_binds - @conn.exec_query('create table ex(id int, data string)') - result = @conn.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 - - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length - - assert_equal [[1, 'foo']], result.rows + with_example_table 'id int, data string' do + result = @conn.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 + + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end end def test_exec_query_with_binds - @conn.exec_query('create table ex(id int, data string)') - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + with_example_table 'id int, data string' do + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_exec_query_typecasts_bind_vals - @conn.exec_query('create table ex(id int, data string)') - @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @conn.columns('ex').find { |col| col.name == 'id' } + with_example_table 'id int, data string' do + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @conn.columns('ex').find { |col| col.name == 'id' } - result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + result = @conn.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_quote_binary_column_escapes_it DualEncoding.connection.execute(<<-eosql) - CREATE TABLE dual_encodings ( + CREATE TABLE IF NOT EXISTS dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, name varchar(255), data binary ) eosql str = "\x80".force_encoding("ASCII-8BIT") - binary = DualEncoding.new :name => 'いただきます!', :data => str + binary = DualEncoding.new name: 'いただきます!', data: str binary.save! assert_equal str, binary.data - ensure - DualEncoding.connection.drop_table('dual_encodings') + DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings') end def test_type_cast_should_not_mutate_encoding name = 'hello'.force_encoding(Encoding::ASCII_8BIT) Owner.create(name: name) assert_equal Encoding::ASCII_8BIT, name.encoding + ensure + Owner.delete_all end def test_execute - @conn.execute "INSERT INTO items (number) VALUES (10)" - records = @conn.execute "SELECT * FROM items" - assert_equal 1, records.length - - record = records.first - assert_equal 10, record['number'] - assert_equal 1, record['id'] + with_example_table do + @conn.execute "INSERT INTO ex (number) VALUES (10)" + records = @conn.execute "SELECT * FROM ex" + assert_equal 1, records.length + + record = records.first + assert_equal 10, record['number'] + assert_equal 1, record['id'] + end end def test_quote_string @@ -223,128 +221,141 @@ module ActiveRecord end def test_insert_sql - 2.times do |i| - rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})" - assert_equal(i + 1, rv) + with_example_table do + 2.times do |i| + rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})" + assert_equal(i + 1, rv) + end + + records = @conn.execute "SELECT * FROM ex" + assert_equal 2, records.length end - - records = @conn.execute "SELECT * FROM items" - assert_equal 2, records.length end def test_insert_sql_logged - sql = "INSERT INTO items (number) VALUES (10)" - name = "foo" - - assert_logged([[sql, name, []]]) do - @conn.insert_sql sql, name + with_example_table do + sql = "INSERT INTO ex (number) VALUES (10)" + name = "foo" + assert_logged [[sql, name, []]] do + @conn.insert_sql sql, name + end end end def test_insert_id_value_returned - sql = "INSERT INTO items (number) VALUES (10)" - idval = 'vuvuzela' - id = @conn.insert_sql sql, nil, nil, idval - assert_equal idval, id + with_example_table do + sql = "INSERT INTO ex (number) VALUES (10)" + idval = 'vuvuzela' + id = @conn.insert_sql sql, nil, nil, idval + assert_equal idval, id + end end def test_select_rows - 2.times do |i| - @conn.create "INSERT INTO items (number) VALUES (#{i})" + with_example_table do + 2.times do |i| + @conn.create "INSERT INTO ex (number) VALUES (#{i})" + end + rows = @conn.select_rows 'select number, id from ex' + assert_equal [[0, 1], [1, 2]], rows end - rows = @conn.select_rows 'select number, id from items' - assert_equal [[0, 1], [1, 2]], rows end def test_select_rows_logged - sql = "select * from items" - name = "foo" - - assert_logged([[sql, name, []]]) do - @conn.select_rows sql, name + with_example_table do + sql = "select * from ex" + name = "foo" + assert_logged [[sql, name, []]] do + @conn.select_rows sql, name + end end end def test_transaction - count_sql = 'select count(*) from items' + with_example_table do + count_sql = 'select count(*) from ex' - @conn.begin_db_transaction - @conn.create "INSERT INTO items (number) VALUES (10)" + @conn.begin_db_transaction + @conn.create "INSERT INTO ex (number) VALUES (10)" - assert_equal 1, @conn.select_rows(count_sql).first.first - @conn.rollback_db_transaction - assert_equal 0, @conn.select_rows(count_sql).first.first + assert_equal 1, @conn.select_rows(count_sql).first.first + @conn.rollback_db_transaction + assert_equal 0, @conn.select_rows(count_sql).first.first + end end def test_tables - assert_equal %w{ items }, @conn.tables - - @conn.execute <<-eosql - CREATE TABLE people ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer - ) - eosql - assert_equal %w{ items people }.sort, @conn.tables.sort + with_example_table do + assert_equal %w{ ex }, @conn.tables + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do + assert_equal %w{ ex people }.sort, @conn.tables.sort + end + end end def test_tables_logs_name - assert_logged [['SCHEMA', []]] do + sql = <<-SQL + SELECT name FROM sqlite_master + WHERE type = 'table' AND NOT name = 'sqlite_sequence' + SQL + assert_logged [[sql.squish, 'SCHEMA', []]] do @conn.tables('hello') - assert_not_nil @subscriber.logged.first.shift end end def test_indexes_logs_name - assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do - @conn.indexes('items', 'hello') + with_example_table do + assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do + @conn.indexes('ex', 'hello') + end end end def test_table_exists_logs_name - assert @conn.table_exists?('items') - assert_equal 'SCHEMA', @subscriber.logged[0][1] + with_example_table do + sql = <<-SQL + SELECT name FROM sqlite_master + WHERE type = 'table' + AND NOT name = 'sqlite_sequence' AND name = \"ex\" + SQL + assert_logged [[sql.squish, 'SCHEMA', []]] do + assert @conn.table_exists?('ex') + end + end end def test_columns - columns = @conn.columns('items').sort_by { |x| x.name } - assert_equal 2, columns.length - assert_equal %w{ id number }.sort, columns.map { |x| x.name } - assert_equal [nil, nil], columns.map { |x| x.default } - assert_equal [true, true], columns.map { |x| x.null } + with_example_table do + columns = @conn.columns('ex').sort_by { |x| x.name } + assert_equal 2, columns.length + assert_equal %w{ id number }.sort, columns.map { |x| x.name } + assert_equal [nil, nil], columns.map { |x| x.default } + assert_equal [true, true], columns.map { |x| x.null } + end end def test_columns_with_default - @conn.execute <<-eosql - CREATE TABLE columns_with_default ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer default 10 - ) - eosql - column = @conn.columns('columns_with_default').find { |x| - x.name == 'number' - } - assert_equal 10, column.default + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do + column = @conn.columns('ex').find { |x| + x.name == 'number' + } + assert_equal 10, column.default + end end def test_columns_with_not_null - @conn.execute <<-eosql - CREATE TABLE columns_with_default ( - id integer PRIMARY KEY AUTOINCREMENT, - number integer not null - ) - eosql - column = @conn.columns('columns_with_default').find { |x| - x.name == 'number' - } - assert !column.null, "column should not be null" + with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do + column = @conn.columns('ex').find { |x| x.name == 'number' } + assert_not column.null, "column should not be null" + end end def test_indexes_logs - assert_difference('@subscriber.logged.length') do - @conn.indexes('items') + with_example_table do + assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do + @conn.indexes('ex') + end end - assert_match(/items/, @subscriber.logged.last.first) end def test_no_indexes @@ -352,41 +363,45 @@ module ActiveRecord end def test_index - @conn.add_index 'items', 'id', :unique => true, :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } + with_example_table do + @conn.add_index 'ex', 'id', unique: true, name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } - assert_equal 'items', index.table - assert index.unique, 'index is unique' - assert_equal ['id'], index.columns + assert_equal 'ex', index.table + assert index.unique, 'index is unique' + assert_equal ['id'], index.columns + end end def test_non_unique_index - @conn.add_index 'items', 'id', :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } - assert !index.unique, 'index is not unique' + with_example_table do + @conn.add_index 'ex', 'id', name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + assert_not index.unique, 'index is not unique' + end end def test_compound_index - @conn.add_index 'items', %w{ id number }, :name => 'fun' - index = @conn.indexes('items').find { |idx| idx.name == 'fun' } - assert_equal %w{ id number }.sort, index.columns.sort + with_example_table do + @conn.add_index 'ex', %w{ id number }, name: 'fun' + index = @conn.indexes('ex').find { |idx| idx.name == 'fun' } + assert_equal %w{ id number }.sort, index.columns.sort + end end def test_primary_key - assert_equal 'id', @conn.primary_key('items') - - @conn.execute <<-eosql - CREATE TABLE foos ( - internet integer PRIMARY KEY AUTOINCREMENT, - number integer not null - ) - eosql - assert_equal 'internet', @conn.primary_key('foos') + with_example_table do + assert_equal 'id', @conn.primary_key('ex') + with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do + assert_equal 'internet', @conn.primary_key('foos') + end + end end def test_no_primary_key - @conn.execute 'CREATE TABLE failboat (number integer not null)' - assert_nil @conn.primary_key('failboat') + with_example_table 'number integer not null' do + assert_nil @conn.primary_key('ex') + end end def test_supports_extensions @@ -403,15 +418,15 @@ module ActiveRecord def test_statement_closed db = SQLite3::Database.new(ActiveRecord::Base. - configurations['arunit']['database']) + configurations['arunit']['database']) statement = SQLite3::Statement.new(db, - 'CREATE TABLE statement_test (number integer not null)') + 'CREATE TABLE statement_test (number integer not null)') statement.stubs(:step).raises(SQLite3::BusyException, 'busy') statement.stubs(:columns).once.returns([]) statement.expects(:close).once SQLite3::Statement.stubs(:new).returns(statement) - assert_raise ActiveRecord::StatementInvalid do + assert_raises ActiveRecord::StatementInvalid do @conn.exec_query 'select * from statement_test' end end @@ -419,10 +434,21 @@ module ActiveRecord private def assert_logged logs + subscriber = SQLSubscriber.new + subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber) yield - assert_equal logs, @subscriber.logged + assert_equal logs, subscriber.logged + ensure + ActiveSupport::Notifications.unsubscribe(subscription) end + def with_example_table(definition = nil, table_name = 'ex', &block) + definition ||= <<-SQL + id integer PRIMARY KEY AUTOINCREMENT, + number integer + SQL + super(@conn, table_name, definition, &block) + end end end end |