From acf178575cf2f6238b8565594958141b2fb007a1 Mon Sep 17 00:00:00 2001 From: devil Date: Wed, 30 Sep 2026 11:20:28 +0000 Subject: [PATCH 1/3] #1176: refuse to delete from a fact that is no longer in the factbase --- lib/fbe/delete.rb | 1 + lib/fbe/delete_one.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fbe/delete.rb b/lib/fbe/delete.rb index ee21bf31..23918b4a 100644 --- a/lib/fbe/delete.rb +++ b/lib/fbe/delete.rb @@ -34,6 +34,7 @@ def Fbe.delete(fact, *props, fb: Fbe.fb, id: '_id') # rubocop:disable Metrics/Cy end fb.txn do |fbt| deleted = fbt.query("(eq #{id} #{i})").delete! + raise(Fbe::Error, "No facts by #{id} = #{i}") if deleted.zero? raise(Fbe::Error, "#{deleted} facts share #{id} = #{i}, cannot delete one of them") if deleted > 1 c = fbt.insert f = c diff --git a/lib/fbe/delete_one.rb b/lib/fbe/delete_one.rb index 7e41eefc..f2a0e603 100644 --- a/lib/fbe/delete_one.rb +++ b/lib/fbe/delete_one.rb @@ -36,7 +36,7 @@ def Fbe.delete_one(fact, prop, value, fb: Fbe.fb, id: '_id') before[prop] = nv before.delete(prop) if nv.empty? fb.txn do |fbt| - fbt.query("(eq #{id} #{i})").delete! + raise(Fbe::Error, "No facts by #{id} = #{i}") if fbt.query("(eq #{id} #{i})").delete!.zero? c = fbt.insert f = c while f.instance_variable_defined?(:@fact) || f.instance_variable_defined?(:@origin) From 922939fa82ea02752ef556a47f569d7c55e10fa0 Mon Sep 17 00:00:00 2001 From: devil Date: Wed, 30 Sep 2026 11:21:05 +0000 Subject: [PATCH 2/3] #1176: test that a removed fact is not brought back --- test/fbe/test_delete.rb | 11 +++++++++++ test/fbe/test_delete_one.rb | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/test/fbe/test_delete.rb b/test/fbe/test_delete.rb index a46409e8..384c63d0 100644 --- a/test/fbe/test_delete.rb +++ b/test/fbe/test_delete.rb @@ -15,6 +15,17 @@ # Copyright:: Copyright (c) 2024-2026 Zerocracy # License:: MIT class TestDelete < Fbe::Test + def test_refuses_to_bring_back_a_removed_fact + fb = Factbase.new + f = fb.insert + f._id = 1 + f.k = 1 + f.t = 5 + fb.query('(eq _id 1)').delete! + assert_raises(Fbe::Error) { Fbe.delete(f, 't', fb:) } + assert_equal(0, fb.size) + end + def test_deletes_one_property fb = Factbase.new f = fb.insert diff --git a/test/fbe/test_delete_one.rb b/test/fbe/test_delete_one.rb index d3e421ce..9f3b28e6 100644 --- a/test/fbe/test_delete_one.rb +++ b/test/fbe/test_delete_one.rb @@ -15,6 +15,17 @@ # Copyright:: Copyright (c) 2024-2026 Zerocracy # License:: MIT class TestDeleteOne < Fbe::Test + def test_refuses_to_bring_back_a_removed_fact + fb = Factbase.new + f = fb.insert + f._id = 1 + f.k = 1 + f.t = 5 + fb.query('(eq _id 1)').delete! + assert_raises(Fbe::Error) { Fbe.delete_one(f, 't', 5, fb:) } + assert_equal(0, fb.size) + end + def test_deletes_one_value fb = Factbase.new f = fb.insert From 86c6cad34bee22d2d1897ed410f9e0610cf3a575 Mon Sep 17 00:00:00 2001 From: devil Date: Wed, 30 Sep 2026 11:21:26 +0000 Subject: [PATCH 3/3] #1176: keep delete_one within the complexity limits of rubocop --- lib/fbe/delete_one.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fbe/delete_one.rb b/lib/fbe/delete_one.rb index f2a0e603..90ce780f 100644 --- a/lib/fbe/delete_one.rb +++ b/lib/fbe/delete_one.rb @@ -18,7 +18,7 @@ # @param [Factbase] fb The factbase to use (defaults to Fbe.fb) # @param [String] id The property name used as unique identifier (defaults to '_id') # @return [nil] Nothing -def Fbe.delete_one(fact, prop, value, fb: Fbe.fb, id: '_id') +def Fbe.delete_one(fact, prop, value, fb: Fbe.fb, id: '_id') # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity raise(Fbe::Error, 'The fact is nil') if fact.nil? prop = prop.to_s i = fact[id]