mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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.
This commit is contained in:
parent
f5d66cb3e5
commit
974f5fbbc2
2 changed files with 74 additions and 45 deletions
|
@ -95,6 +95,8 @@ module ActiveRecord
|
|||
|
||||
@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 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue