提交 974f5fbb 编写于 作者: R Ryuta Kamizono

Translate Foreign Key violation to the specific exception for SQLite3 adapter

Raise `ActiveRecord::InvalidForeignKey` when a record cannot be inserted
or updated because it references a non-existent record for SQLite3
adapter.
上级 f5d66cb3
......@@ -95,6 +95,8 @@ def initialize(connection, logger, connection_options, config)
@active = nil
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
configure_connection
end
def supports_ddl_transactions?
......@@ -185,6 +187,19 @@ def supports_explain?
true
end
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
old = select_value("PRAGMA foreign_keys")
begin
execute("PRAGMA foreign_keys = OFF")
yield
ensure
execute("PRAGMA foreign_keys = #{old}")
end
end
#--
# DATABASE STATEMENTS ======================================
#++
......@@ -525,6 +540,8 @@ def translate_exception(exception, message)
RecordNotUnique.new(message)
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
NotNullViolation.new(message)
when /FOREIGN KEY constraint failed/i
InvalidForeignKey.new(message)
else
super
end
......@@ -574,6 +591,10 @@ def table_structure_with_collation(table_name, basic_structure)
def create_table_definition(*args)
SQLite3::TableDefinition.new(*args)
end
def configure_connection
execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
end
end
end
......@@ -185,34 +185,6 @@ def test_not_null_violations_are_translated_to_specific_exception
end
unless current_adapter?(:SQLite3Adapter)
def test_foreign_key_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::InvalidForeignKey) do
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
@connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
end
assert_not_nil error.cause
end
def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
klass_has_fk = Class.new(ActiveRecord::Base) do
self.table_name = "fk_test_has_fk"
end
error = assert_raises(ActiveRecord::InvalidForeignKey) do
has_fk = klass_has_fk.new
has_fk.fk_id = 1231231231
has_fk.save(validate: false)
end
assert_not_nil error.cause
end
def test_value_limit_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::ValueTooLong) do
Event.create(title: "abcdefgh")
......@@ -230,23 +202,6 @@ def test_numeric_value_out_of_ranges_are_translated_to_specific_exception
end
end
def test_disable_referential_integrity
assert_nothing_raised do
@connection.disable_referential_integrity do
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
@connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
# should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block
# and will fail (at least on Oracle)
@connection.execute "DELETE FROM fk_test_has_fk"
end
end
end
def test_select_all_always_return_activerecord_result
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
......@@ -290,6 +245,59 @@ def test_log_invalid_encoding
end
end
class AdapterForeignKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
def setup
@connection = ActiveRecord::Base.connection
end
def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
klass_has_fk = Class.new(ActiveRecord::Base) do
self.table_name = "fk_test_has_fk"
end
error = assert_raises(ActiveRecord::InvalidForeignKey) do
has_fk = klass_has_fk.new
has_fk.fk_id = 1231231231
has_fk.save(validate: false)
end
assert_not_nil error.cause
end
def test_foreign_key_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::InvalidForeignKey) do
insert_into_fk_test_has_fk
end
assert_not_nil error.cause
end
def test_disable_referential_integrity
assert_nothing_raised do
@connection.disable_referential_integrity do
insert_into_fk_test_has_fk
# should delete created record as otherwise disable_referential_integrity will try to enable constraints
# after executed block and will fail (at least on Oracle)
@connection.execute "DELETE FROM fk_test_has_fk"
end
end
end
private
def insert_into_fk_test_has_fk
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
@connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)"
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
self.use_transactional_tests = false
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册