factbase 0.7.3 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/benchmark.yml +3 -1
- data/.gitignore +5 -5
- data/.rultor.yml +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +4 -4
- data/README.md +29 -25
- data/REUSE.toml +4 -0
- data/Rakefile +10 -3
- data/benchmark/bench_factbase.rb +56 -0
- data/benchmark/bench_query.rb +41 -0
- data/benchmark/bench_taped.rb +33 -0
- data/lib/factbase/logged.rb +172 -0
- data/lib/factbase/looged.rb +1 -1
- data/lib/factbase/taped.rb +23 -24
- data/lib/factbase.rb +3 -5
- data/test/factbase/terms/test_aggregates.rb +2 -2
- data/test/factbase/terms/test_aliases.rb +4 -4
- data/test/factbase/terms/test_casting.rb +1 -2
- data/test/factbase/terms/test_debug.rb +2 -2
- data/test/factbase/terms/test_defn.rb +2 -2
- data/test/factbase/terms/test_logical.rb +2 -2
- data/test/factbase/terms/test_math.rb +1 -2
- data/test/factbase/terms/test_meta.rb +2 -2
- data/test/factbase/terms/test_ordering.rb +2 -2
- data/test/factbase/terms/test_strings.rb +2 -2
- data/test/factbase/terms/test_system.rb +1 -2
- data/test/factbase/test_accum.rb +2 -2
- data/test/factbase/test_churn.rb +2 -2
- data/test/factbase/test_fact.rb +2 -2
- data/test/factbase/test_flatten.rb +2 -2
- data/test/factbase/test_inv.rb +2 -2
- data/test/factbase/test_logged.rb +129 -0
- data/test/factbase/test_looged.rb +2 -2
- data/test/factbase/test_pre.rb +2 -2
- data/test/factbase/test_query.rb +2 -2
- data/test/factbase/test_rules.rb +2 -2
- data/test/factbase/test_syntax.rb +2 -2
- data/test/factbase/test_tallied.rb +2 -2
- data/test/factbase/test_taped.rb +2 -2
- data/test/factbase/test_tee.rb +2 -2
- data/test/factbase/test_term.rb +2 -2
- data/test/factbase/test_to_json.rb +2 -2
- data/test/factbase/test_to_xml.rb +2 -2
- data/test/factbase/test_to_yaml.rb +2 -2
- data/test/test__helper.rb +3 -1
- data/test/test_factbase.rb +5 -5
- metadata +8 -7
- data/benchmarks/simple.rb +0 -127
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20e78ab8a1e9e32a777880d99f15cb339c22310b152fcecc626847a364536059
|
4
|
+
data.tar.gz: 480617fd3b19baa46c43553eb6eb87accc4bd4faa8c28a7d121803b1e5f34da0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
data/.rultor.yml
CHANGED
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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/
|
71
|
+
require 'factbase/logged'
|
72
72
|
log = Loog::VERBOSE
|
73
|
-
fb = Factbase::
|
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/
|
81
|
+
require 'factbase/tallied'
|
82
82
|
log = Loog::VERBOSE
|
83
|
-
fb = Factbase::
|
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
|
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
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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-
|
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/
|
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 '
|
43
|
+
desc 'Benchmark them all'
|
44
44
|
task :benchmark do
|
45
|
-
|
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
|
@@ -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
|
data/lib/factbase/looged.rb
CHANGED
@@ -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 #{@
|
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
|
data/lib/factbase/taped.rb
CHANGED
@@ -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
|
-
|
16
|
-
|
17
|
-
def initialize(origin, lookup: {})
|
15
|
+
def initialize(origin)
|
18
16
|
@origin = origin
|
19
|
-
@inserted =
|
20
|
-
@deleted =
|
21
|
-
@added =
|
22
|
-
|
17
|
+
@inserted = []
|
18
|
+
@deleted = []
|
19
|
+
@added = []
|
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
|
-
|
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
|
-
|
38
|
-
@lookup[map.object_id] = map
|
39
|
-
# rubocop:enable Lint/HashCompareByIdentity
|
40
|
-
@inserted.add(map.object_id)
|
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.add(m.object_id)
|
58
|
-
end
|
57
|
+
@deleted.append(m.object_id) if r
|
59
58
|
r
|
60
59
|
end
|
61
60
|
end
|
@@ -75,7 +74,7 @@ class Factbase::Taped
|
|
75
74
|
|
76
75
|
def []=(key, value)
|
77
76
|
@origin[key] = value
|
78
|
-
@added.
|
77
|
+
@added.append(@origin.object_id)
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
@@ -104,12 +103,12 @@ class Factbase::Taped
|
|
104
103
|
end
|
105
104
|
|
106
105
|
def <<(item)
|
107
|
-
@added.
|
106
|
+
@added.append(@oid)
|
108
107
|
@origin << (item)
|
109
108
|
end
|
110
109
|
|
111
110
|
def uniq!
|
112
|
-
@added.
|
111
|
+
@added.append(@oid)
|
113
112
|
@origin.uniq!
|
114
113
|
end
|
115
114
|
end
|