factbase 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +3 -1
  3. data/.gitignore +5 -5
  4. data/.rultor.yml +1 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +4 -4
  7. data/README.md +29 -25
  8. data/REUSE.toml +4 -0
  9. data/Rakefile +10 -3
  10. data/benchmark/bench_factbase.rb +56 -0
  11. data/benchmark/bench_query.rb +41 -0
  12. data/benchmark/bench_taped.rb +33 -0
  13. data/lib/factbase/churn.rb +4 -0
  14. data/lib/factbase/logged.rb +172 -0
  15. data/lib/factbase/looged.rb +2 -2
  16. data/lib/factbase/taped.rb +16 -18
  17. data/lib/factbase.rb +10 -11
  18. data/test/factbase/terms/test_aggregates.rb +2 -2
  19. data/test/factbase/terms/test_aliases.rb +4 -4
  20. data/test/factbase/terms/test_casting.rb +1 -2
  21. data/test/factbase/terms/test_debug.rb +2 -2
  22. data/test/factbase/terms/test_defn.rb +2 -2
  23. data/test/factbase/terms/test_logical.rb +2 -2
  24. data/test/factbase/terms/test_math.rb +1 -2
  25. data/test/factbase/terms/test_meta.rb +2 -2
  26. data/test/factbase/terms/test_ordering.rb +2 -2
  27. data/test/factbase/terms/test_strings.rb +2 -2
  28. data/test/factbase/terms/test_system.rb +1 -2
  29. data/test/factbase/test_accum.rb +2 -2
  30. data/test/factbase/test_churn.rb +3 -2
  31. data/test/factbase/test_fact.rb +2 -2
  32. data/test/factbase/test_flatten.rb +2 -2
  33. data/test/factbase/test_inv.rb +2 -2
  34. data/test/factbase/test_logged.rb +129 -0
  35. data/test/factbase/test_looged.rb +8 -8
  36. data/test/factbase/test_pre.rb +2 -2
  37. data/test/factbase/test_query.rb +2 -2
  38. data/test/factbase/test_rules.rb +2 -2
  39. data/test/factbase/test_syntax.rb +2 -2
  40. data/test/factbase/test_tallied.rb +2 -2
  41. data/test/factbase/test_taped.rb +12 -2
  42. data/test/factbase/test_tee.rb +2 -2
  43. data/test/factbase/test_term.rb +2 -2
  44. data/test/factbase/test_to_json.rb +2 -2
  45. data/test/factbase/test_to_xml.rb +2 -2
  46. data/test/factbase/test_to_yaml.rb +2 -2
  47. data/test/test__helper.rb +3 -1
  48. data/test/test_factbase.rb +45 -12
  49. metadata +8 -7
  50. data/benchmarks/simple.rb +0 -127
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e04b831fdaead7a6a84de7055d9d9bb47f052f6d0c00cb16d081d0074724cea3
4
- data.tar.gz: e21055583e34664893d25fdfc9e928fed079dd014dc91cd1a2e0832d36301161
3
+ metadata.gz: 20e78ab8a1e9e32a777880d99f15cb339c22310b152fcecc626847a364536059
4
+ data.tar.gz: 480617fd3b19baa46c43553eb6eb87accc4bd4faa8c28a7d121803b1e5f34da0
5
5
  SHA512:
6
- metadata.gz: c62a1fbc9f14bab4a66056a59e911c72b47dbc368deb29cc6e07d010b4f3c5c57d3ee9f881cc2c1292d1771202bd9da9c7a1fdfde3da79134848c14195d2f9d2
7
- data.tar.gz: 657cba190035d6937a37da2446600b071c72b0f84dd468a89e1c429258c6e30ddd51bc0f086ea701afd3a8120fc857902f37a0af6e3dda36abf2b1a99ca03798
6
+ metadata.gz: dd1fcc7943c1c3c0cc0325f1dbbfeac4be919f79d7b456ea9a9a2ffae2548dac88825854c77d8de5ca91bfbc135fd79e227c745a7793cc27a455ef9a0262367d
7
+ data.tar.gz: c4e4f3faf564894f4e57e308fb768906ab59a3e413fb9e45f6f9674e183382d5b7c1616465e6a2ba6382f0a8950ea56a1cef423cbfa662511a3dfded178ff4d1
@@ -23,11 +23,13 @@ jobs:
23
23
  bundler-cache: true
24
24
  - run: bundle config set --global path "$(pwd)/vendor/bundle"
25
25
  - run: bundle install --no-color
26
- - run: bundle exec ruby benchmarks/simple.rb > stdout.txt
26
+ - run: bundle exec rake benchmark > stdout.txt
27
27
  - run: |
28
28
  set -x
29
29
  sum=$(
30
+ echo '```'
30
31
  cat stdout.txt
32
+ echo '```'
31
33
  echo
32
34
  echo "The results were calculated in [this GHA job][benchmark-gha]"
33
35
  echo "on $(date +'%Y-%m-%d') at $(date +'%H:%M'),"
data/.gitignore CHANGED
@@ -1,9 +1,9 @@
1
- coverage/
2
- .idea/
3
1
  *.gem
4
- .bundle/
5
2
  .DS_Store
6
- rdoc/
7
- doc/
3
+ .bundle/
4
+ .idea/
8
5
  .yardoc/
6
+ coverage/
7
+ doc/
8
+ rdoc/
9
9
  vendor/
data/.rultor.yml CHANGED
@@ -3,7 +3,7 @@
3
3
  ---
4
4
  # yamllint disable rule:line-length
5
5
  docker:
6
- image: yegor256/rultor-ruby
6
+ image: yegor256/ruby
7
7
  assets:
8
8
  rubygems.yml: yegor256/home#assets/rubygems.yml
9
9
  install: |
data/Gemfile CHANGED
@@ -10,7 +10,7 @@ gem 'minitest', '5.25.4', require: false
10
10
  gem 'minitest-reporters', '1.7.1', require: false
11
11
  gem 'rake', '13.2.1', require: false
12
12
  gem 'rspec-rails', '7.1.1', require: false
13
- gem 'rubocop', '1.72.2', require: false
13
+ gem 'rubocop', '1.73.1', require: false
14
14
  gem 'rubocop-minitest', '>0', require: false
15
15
  gem 'rubocop-performance', '>0', require: false
16
16
  gem 'rubocop-rake', '>0', require: false
data/Gemfile.lock CHANGED
@@ -148,7 +148,7 @@ GEM
148
148
  rspec-mocks (~> 3.13)
149
149
  rspec-support (~> 3.13)
150
150
  rspec-support (3.13.2)
151
- rubocop (1.72.2)
151
+ rubocop (1.73.1)
152
152
  json (~> 2.3)
153
153
  language_server-protocol (~> 3.17.0.2)
154
154
  lint_roller (~> 1.1.0)
@@ -159,7 +159,7 @@ GEM
159
159
  rubocop-ast (>= 1.38.0, < 2.0)
160
160
  ruby-progressbar (~> 1.7)
161
161
  unicode-display_width (>= 2.4.0, < 4.0)
162
- rubocop-ast (1.38.0)
162
+ rubocop-ast (1.38.1)
163
163
  parser (>= 3.3.1.0)
164
164
  rubocop-minitest (0.37.1)
165
165
  lint_roller (~> 1.1)
@@ -197,7 +197,7 @@ GEM
197
197
  unicode-display_width (3.1.4)
198
198
  unicode-emoji (~> 4.0, >= 4.0.4)
199
199
  unicode-emoji (4.0.4)
200
- uri (1.0.2)
200
+ uri (1.0.3)
201
201
  useragent (0.16.11)
202
202
  yaml (0.4.0)
203
203
  yard (0.9.37)
@@ -218,7 +218,7 @@ DEPENDENCIES
218
218
  minitest-reporters (= 1.7.1)
219
219
  rake (= 13.2.1)
220
220
  rspec-rails (= 7.1.1)
221
- rubocop (= 1.72.2)
221
+ rubocop (= 1.73.1)
222
222
  rubocop-minitest (> 0)
223
223
  rubocop-performance (> 0)
224
224
  rubocop-rake (> 0)
data/README.md CHANGED
@@ -17,7 +17,7 @@ The values are either atomic literals or non-empty sets of literals.
17
17
  It is possible to delete a fact, but impossible to delete a property
18
18
  from a fact.
19
19
 
20
- **ATTENTION**: The current implemention is naive and,
20
+ **ATTENTION**: The current implementation is naive and,
21
21
  because of that, **very slow**. I will be very happy
22
22
  if you suggest a better implementation without the change of the interface.
23
23
  The `Factbase::query()` method is what mostly needs performance optimization:
@@ -68,9 +68,9 @@ You can make a factbase log all operations:
68
68
 
69
69
  ```ruby
70
70
  require 'loog'
71
- require 'factbase/looged'
71
+ require 'factbase/logged'
72
72
  log = Loog::VERBOSE
73
- fb = Factbase::Looged.new(Factbase.new, log)
73
+ fb = Factbase::Logged.new(Factbase.new, log)
74
74
  f = fb.insert
75
75
  ```
76
76
 
@@ -78,9 +78,9 @@ You can also count the amount of changes made to a factbase:
78
78
 
79
79
  ```ruby
80
80
  require 'loog'
81
- require 'factbase/tailled'
81
+ require 'factbase/tallied'
82
82
  log = Loog::VERBOSE
83
- fb = Factbase::Tailled.new(Factbase.new, log)
83
+ fb = Factbase::Tallied.new(Factbase.new, log)
84
84
  f = fb.insert
85
85
  churn = fb.churn
86
86
  assert churn.inserted == 1
@@ -137,7 +137,7 @@ Also, some simple arithmetic:
137
137
  * `(div v1 v2)` is a division of `∏v1` by `∏v2`
138
138
 
139
139
  It's possible to add and deduct string values to time values, like
140
- `(plus t '2 days')` or ``(minus t '14 hours')``.
140
+ `(plus t '2 days')` or `(minus t '14 hours')`.
141
141
 
142
142
  Types may be converted:
143
143
 
@@ -190,7 +190,7 @@ There are some system-level terms:
190
190
 
191
191
  Read
192
192
  [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
193
- Make sure you build is green before you contribute
193
+ Make sure your build is green before you contribute
194
194
  your pull request. You will need to have
195
195
  [Ruby](https://www.ruby-lang.org/en/) 3.2+ and
196
196
  [Bundler](https://bundler.io/) installed. Then:
@@ -207,26 +207,30 @@ If it's clean and you don't see any error messages, submit your pull request.
207
207
  This is the result of the benchmark:
208
208
 
209
209
  <!-- benchmark_begin -->
210
- | Action | Seconds | Details |
211
- | --- | --: | --- |
212
- | `fb.insert()` | 1.883 | Inserted 25000 facts |
213
- | `(gt time '2024-03-23T03:21:43Z')` | 0.201 | 25000 facts x100 |
214
- | `(gt cost 50)` | 0.175 | 12430 facts x100 |
215
- | `(eq title 'Object Thinking 5000')` | 0.129 | 1 facts x100 |
216
- | `(and (eq foo 42.998) (or (gt bar 200) (absent zzz)))` | 0.157 | 0 facts x100 |
217
- | `(eq id (agg (always) (max id)))` | 0.268 | 1 facts x100 |
218
- | `(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))` | 1.844 | 25000 facts x100 |
219
- | txn: `query()` | 19.358 | modified 0 facts |
220
- | txn: `insert()` | 0.059 | modified 100 facts |
221
- | txn: `add()` | 16.223 | modified 3 facts |
222
- | txn: `delete!()` | 3.573 | modified 12439 facts |
223
- | `.export()` + `.import()` | 0.369 | 1451040 bytes |
224
- | `(gt cost 3)` | 0.030 | Deleted 12395 fact(s) |
225
- | `(gt bar 1)` | 0.001 | Deleted 363 fact(s) |
210
+ ```
211
+ user system total real
212
+ insert 50000 facts 1.216926 0.009687 1.226613 ( 1.233335)
213
+ export 50000 facts 0.055611 0.002976 0.058587 ( 0.058594)
214
+ import 1008464 bytes (50000 facts) 0.068731 0.008049 0.076780 ( 0.076828)
215
+ insert 10 facts 0.096866 0.019001 0.115867 ( 0.115919)
216
+ query 10 times 3.940857 0.141913 4.082770 ( 4.089283)
217
+ modify 10 attrs 3.430524 0.064981 3.495505 ( 3.499871)
218
+ delete 10 facts 2.130973 0.001001 2.131974 ( 2.132124)
219
+ (gt time '2024-03-23T03:21:43Z') 0.192254 0.005977 0.198231 ( 0.198500)
220
+ (gt cost 50) 0.201262 0.004008 0.205270 ( 0.205516)
221
+ (eq title 'Object Thinking 5000') 0.201111 0.003973 0.205084 ( 0.205312)
222
+ (and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.304289 0.002010 0.306299 ( 0.306505)
223
+ (eq id (agg (always) (max id))) 0.399487 0.002987 0.402474 ( 0.402837)
224
+ (join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 4.501088 0.016998 4.518086 ( 4.519644)
225
+ delete! 0.117293 0.000007 0.117300 ( 0.117330)
226
+ Taped.append() x50000 0.028056 0.001978 0.030034 ( 0.030038)
227
+ Taped.each() x125 1.332449 0.002015 1.334464 ( 1.334506)
228
+ Taped.delete_if() x375 0.820585 0.000000 0.820585 ( 0.820626)
229
+ ```
226
230
 
227
231
  The results were calculated in [this GHA job][benchmark-gha]
228
- on 2025-02-24 at 13:32,
232
+ on 2025-02-28 at 09:13,
229
233
  on Linux with 4 CPUs.
230
234
  <!-- benchmark_end -->
231
235
 
232
- [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13499241189
236
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13584960859
data/REUSE.toml CHANGED
@@ -4,13 +4,17 @@
4
4
  version = 1
5
5
  [[annotations]]
6
6
  path = [
7
+ "**/*.csv",
7
8
  "**/*.jpg",
8
9
  "**/*.json",
9
10
  "**/*.md",
10
11
  "**/*.pdf",
11
12
  "**/*.png",
12
13
  "**/*.svg",
14
+ "**/*.txt",
15
+ "**/*.vm",
13
16
  "**/.gitignore",
17
+ "**/CNAME",
14
18
  ".gitattributes",
15
19
  ".gitignore",
16
20
  ".gitleaksignore",
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ def version
15
15
  Gem::Specification.load(Dir['*.gemspec'].first).version
16
16
  end
17
17
 
18
- task default: %i[clean test rubocop yard]
18
+ task default: %i[clean test rubocop yard benchmark]
19
19
 
20
20
  require 'rake/testtask'
21
21
  desc 'Run all unit tests'
@@ -40,7 +40,14 @@ RuboCop::RakeTask.new(:rubocop) do |task|
40
40
  task.requires << 'rubocop-rspec'
41
41
  end
42
42
 
43
- desc 'Run benchmark script'
43
+ desc 'Benchmark them all'
44
44
  task :benchmark do
45
- ruby 'benchmarks/simple.rb'
45
+ fb = Factbase.new
46
+ require 'benchmark'
47
+ Benchmark.bm(60) do |b|
48
+ Dir['benchmark/bench_*.rb'].each do |f|
49
+ require_relative f
50
+ Kernel.send(File.basename(f).gsub(/\.rb$/, '').to_sym, b, fb)
51
+ end
52
+ end
46
53
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../lib/factbase'
7
+
8
+ def bench_factbase(bmk, fb)
9
+ total = 50_000
10
+ bmk.report("insert #{total} facts") do
11
+ total.times do
12
+ fact = fb.insert
13
+ fact.foo = rand(0.0..100.0).round(3)
14
+ fact.bar = rand(100..300)
15
+ end
16
+ end
17
+
18
+ bin = nil
19
+ bmk.report("export #{total} facts") do
20
+ bin = fb.export
21
+ end
22
+ bmk.report("import #{bin.size} bytes (#{total} facts)") do
23
+ fb2 = Factbase.new
24
+ fb2.import(bin)
25
+ end
26
+
27
+ actions = 10
28
+ bmk.report("insert #{actions} facts") do
29
+ fb.txn do |fbt|
30
+ actions.times do
31
+ fbt.insert.z = rand(0..100)
32
+ end
33
+ end
34
+ end
35
+ bmk.report("query #{actions} times") do
36
+ fb.txn do |fbt|
37
+ actions.times do |i|
38
+ fbt.query("(gt foo #{i})").each.to_a
39
+ end
40
+ end
41
+ end
42
+ bmk.report("modify #{actions} attrs") do
43
+ fb.txn do |fbt|
44
+ actions.times do |i|
45
+ fbt.query("(gt foo #{i})").each.to_a.first.bar = 55
46
+ end
47
+ end
48
+ end
49
+ bmk.report("delete #{actions} facts") do
50
+ fb.txn do |fbt|
51
+ actions.times do |i|
52
+ fbt.query("(gt foo #{100 - i})").delete!
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../lib/factbase'
7
+
8
+ def bench_query(bmk, fb)
9
+ total = 5_000
10
+ total.times do |i|
11
+ f = fb.insert
12
+ f.id = i
13
+ f.title = "Object Thinking #{i}"
14
+ f.time = Time.now.iso8601
15
+ f.cost = rand(1..100)
16
+ f.foo = rand(0.0..100.0).round(3)
17
+ f.bar = rand(100..300)
18
+ f.seenBy = "User#{i}" if i.even?
19
+ f.zzz = "Extra#{i}" if (i % 10).zero?
20
+ end
21
+
22
+ runs = 10
23
+ [
24
+ '(gt time \'2024-03-23T03:21:43Z\')',
25
+ '(gt cost 50)',
26
+ '(eq title \'Object Thinking 5000\')',
27
+ '(and (eq foo 42.998) (or (gt bar 200) (absent zzz)))',
28
+ '(eq id (agg (always) (max id)))',
29
+ '(join "c<=cost,b<=bar" (eq id (agg (always) (max id))))'
30
+ ].each do |q|
31
+ bmk.report(q) do
32
+ runs.times do
33
+ fb.query(q).each.to_a
34
+ end
35
+ end
36
+ end
37
+
38
+ bmk.report('delete!') do
39
+ fb.query('(gt foo 50)').delete!
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../lib/factbase'
7
+ require_relative '../lib/factbase/taped'
8
+
9
+ def bench_taped(bmk, _fb)
10
+ maps = []
11
+ taped = Factbase::Taped.new(maps)
12
+
13
+ cycles = 50_000
14
+ bmk.report("Taped.append() x#{cycles}") do
15
+ cycles.times do
16
+ taped << { foo: rand(0..100) }
17
+ end
18
+ end
19
+
20
+ cycles /= 400
21
+ bmk.report("Taped.each() x#{cycles}") do
22
+ cycles.times do
23
+ taped.each.to_a
24
+ end
25
+ end
26
+
27
+ cycles *= 3
28
+ bmk.report("Taped.delete_if() x#{cycles}") do
29
+ cycles.times do
30
+ taped.delete_if { |m| m[:foo] < 50 }
31
+ end
32
+ end
33
+ end
@@ -26,6 +26,10 @@ class Factbase::Churn
26
26
  @inserted.zero? && @deleted.zero? && @added.zero?
27
27
  end
28
28
 
29
+ def to_i
30
+ @inserted + @deleted + @added
31
+ end
32
+
29
33
  def append(ins, del, add)
30
34
  @mutex.synchronize do
31
35
  @inserted += ins
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'decoor'
7
+ require 'others'
8
+ require 'time'
9
+ require 'loog'
10
+ require 'tago'
11
+ require_relative 'syntax'
12
+
13
+ # A decorator of a Factbase, that logs all operations.
14
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
15
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
16
+ # License:: MIT
17
+ class Factbase::Logged
18
+ def initialize(fb, loog)
19
+ raise 'The "fb" is nil' if fb.nil?
20
+ @fb = fb
21
+ raise 'The "loog" is nil' if loog.nil?
22
+ @loog = loog
23
+ end
24
+
25
+ decoor(:fb)
26
+
27
+ def insert
28
+ start = Time.now
29
+ f = @fb.insert
30
+ @loog.debug("Inserted new fact ##{@fb.size} in #{start.ago}")
31
+ Fact.new(f, @loog)
32
+ end
33
+
34
+ def query(query)
35
+ Query.new(@fb, query, @loog)
36
+ end
37
+
38
+ def txn
39
+ start = Time.now
40
+ id = nil
41
+ rollback = false
42
+ r =
43
+ @fb.txn do |fbt|
44
+ id = fbt.object_id
45
+ yield Factbase::Logged.new(fbt, @loog)
46
+ rescue Factbase::Rollback => e
47
+ rollback = true
48
+ raise e
49
+ end
50
+ if rollback
51
+ @loog.debug("Txn ##{id} rolled back in #{start.ago}")
52
+ else
53
+ @loog.debug("Txn ##{id} touched #{r} in #{start.ago}")
54
+ end
55
+ r
56
+ end
57
+
58
+ # Fact decorator.
59
+ #
60
+ # This is an internal class, it is not supposed to be instantiated directly.
61
+ #
62
+ class Fact
63
+ MAX_LENGTH = 64
64
+
65
+ def initialize(fact, loog)
66
+ @fact = fact
67
+ @loog = loog
68
+ end
69
+
70
+ def to_s
71
+ @fact.to_s
72
+ end
73
+
74
+ def all_properties
75
+ @fact.all_properties
76
+ end
77
+
78
+ others do |*args|
79
+ r = @fact.method_missing(*args)
80
+ k = args[0].to_s
81
+ v = args[1]
82
+ s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
83
+ s = v.to_s.inspect if v.is_a?(String)
84
+ s = "#{s[0..MAX_LENGTH / 2]}...#{s[-MAX_LENGTH / 2..]}" if s.length > MAX_LENGTH
85
+ @loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
86
+ r
87
+ end
88
+ end
89
+
90
+ # Query decorator.
91
+ #
92
+ # This is an internal class, it is not supposed to be instantiated directly.
93
+ #
94
+ class Query
95
+ def initialize(fb, expr, loog)
96
+ @fb = fb
97
+ @expr = expr
98
+ @loog = loog
99
+ end
100
+
101
+ def one(params = {})
102
+ q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
103
+ r = nil
104
+ tail =
105
+ Factbase::Logged.elapsed do
106
+ r = @fb.query(@expr).one(params)
107
+ end
108
+ if r.nil?
109
+ @loog.debug("Nothing found by '#{q}' #{tail}")
110
+ else
111
+ @loog.debug("Found #{r} (#{r.class}) by '#{q}' #{tail}")
112
+ end
113
+ r
114
+ end
115
+
116
+ def each(params = {}, &)
117
+ q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
118
+ if block_given?
119
+ r = nil
120
+ tail =
121
+ Factbase::Logged.elapsed do
122
+ r = @fb.query(@expr).each(params, &)
123
+ end
124
+ raise ".each of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
125
+ if r.zero?
126
+ @loog.debug("Nothing found by '#{q}' #{tail}")
127
+ else
128
+ @loog.debug("Found #{r} fact(s) by '#{q}' #{tail}")
129
+ end
130
+ r
131
+ else
132
+ array = []
133
+ tail =
134
+ Factbase::Logged.elapsed do
135
+ @fb.query(@expr).each(params) do |f|
136
+ array << f
137
+ end
138
+ end
139
+ if array.empty?
140
+ @loog.debug("Nothing found by '#{q}' #{tail}")
141
+ else
142
+ @loog.debug("Found #{array.size} fact(s) by '#{q}' #{tail}")
143
+ end
144
+ array
145
+ end
146
+ end
147
+
148
+ def delete!
149
+ r = nil
150
+ before = @fb.size
151
+ tail =
152
+ Factbase::Logged.elapsed do
153
+ r = @fb.query(@expr).delete!
154
+ end
155
+ raise ".delete! of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
156
+ if before.zero?
157
+ @loog.debug("There were no facts, nothing deleted by '#{@expr}' #{tail}")
158
+ elsif r.zero?
159
+ @loog.debug("No facts out of #{before} deleted by '#{@expr}' #{tail}")
160
+ else
161
+ @loog.debug("Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
162
+ end
163
+ r
164
+ end
165
+ end
166
+
167
+ def self.elapsed
168
+ start = Time.now
169
+ yield
170
+ "in #{start.ago}"
171
+ end
172
+ end
@@ -50,7 +50,7 @@ class Factbase::Looged
50
50
  if rollback
51
51
  @loog.debug("Txn ##{id} rolled back in #{start.ago}")
52
52
  else
53
- @loog.debug("Txn ##{id} #{r.positive? ? 'modified' : 'didn\'t touch'} the factbase in #{start.ago}")
53
+ @loog.debug("Txn ##{id} touched #{r} in #{start.ago}")
54
54
  end
55
55
  r
56
56
  end
@@ -121,7 +121,7 @@ class Factbase::Looged
121
121
  Factbase::Looged.elapsed do
122
122
  r = @fb.query(@expr).each(params, &)
123
123
  end
124
- raise ".each of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
124
+ raise ".each of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
125
125
  if r.zero?
126
126
  @loog.debug("Nothing found by '#{q}' #{tail}")
127
127
  else
@@ -12,20 +12,27 @@ require_relative '../factbase'
12
12
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
13
  # License:: MIT
14
14
  class Factbase::Taped
15
- attr_reader :inserted, :deleted, :added
16
-
17
- def initialize(origin, lookup: {})
15
+ def initialize(origin)
18
16
  @origin = origin
19
17
  @inserted = []
20
18
  @deleted = []
21
19
  @added = []
22
- @lookup = lookup
20
+ end
21
+
22
+ def inserted
23
+ @inserted.uniq
24
+ end
25
+
26
+ def deleted
27
+ @deleted.uniq
28
+ end
29
+
30
+ def added
31
+ @added.uniq
23
32
  end
24
33
 
25
34
  def find_by_object_id(oid)
26
- o = @lookup[oid]
27
- o = @origin.find { |m| m.object_id == oid } if o.nil?
28
- o
35
+ @origin.find { |m| m.object_id == oid }
29
36
  end
30
37
 
31
38
  def size
@@ -34,17 +41,12 @@ class Factbase::Taped
34
41
 
35
42
  def <<(map)
36
43
  @origin << (map)
37
- # rubocop:disable Lint/HashCompareByIdentity
38
- @lookup[map.object_id] = map
39
- # rubocop:enable Lint/HashCompareByIdentity
40
44
  @inserted.append(map.object_id)
41
45
  end
42
46
 
43
47
  def each
48
+ return to_enum(__method__) unless block_given?
44
49
  @origin.each do |m|
45
- # rubocop:disable Lint/HashCompareByIdentity
46
- @lookup[m.object_id] = m
47
- # rubocop:enable Lint/HashCompareByIdentity
48
50
  yield TapedHash.new(m, @added)
49
51
  end
50
52
  end
@@ -52,10 +54,7 @@ class Factbase::Taped
52
54
  def delete_if
53
55
  @origin.delete_if do |m|
54
56
  r = yield m
55
- if r
56
- @lookup.delete(m.object_id)
57
- @deleted.append(m.object_id)
58
- end
57
+ @deleted.append(m.object_id) if r
59
58
  r
60
59
  end
61
60
  end
@@ -100,7 +99,6 @@ class Factbase::Taped
100
99
  end
101
100
 
102
101
  def any?(&)
103
- p 1
104
102
  @origin.any?(&)
105
103
  end
106
104