factbase 0.7.4 → 0.7.5
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 +1 -1
- data/.gitignore +1 -0
- data/Gemfile.lock +5 -5
- data/README.md +28 -20
- data/benchmark/bench_factbase.rb +10 -5
- data/benchmark/bench_large_query.rb +128 -0
- data/benchmark/bench_query.rb +1 -1
- data/factbase.gemspec +5 -5
- data/lib/factbase/accum.rb +1 -1
- data/lib/factbase/taped.rb +2 -1
- data/lib/factbase/term.rb +9 -1
- data/lib/factbase/terms/aggregates.rb +2 -2
- data/lib/factbase/terms/meta.rb +1 -1
- data/lib/factbase/terms/ordering.rb +1 -1
- data/lib/factbase.rb +1 -1
- metadata +23 -24
- data/lib/factbase/looged.rb +0 -172
- data/test/factbase/test_looged.rb +0 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a3adc3cc2aec38609bd1d98b4266e18db6d3fc9e0e9eb3714f9772bf6bfee60
|
4
|
+
data.tar.gz: 7eae0cf3bbd8b1d432971f051ad08be1f39df640245d2b0f565c14a95a436aa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee55ea6c2b5c2bd780c5b241bc16c14a2151adcb44829ec15b71c6e6fa614f9c9776e11d7c0bf76e2693a9141c745a0a233946e7b09b9961dc042f2ab22d412a
|
7
|
+
data.tar.gz: 59c44e45b9a6f2bbfe70a7fe1e649fba6c3af8b9d04e9d225289cd32d7736db1e37a138f53ed300b5639fdadbe9cef448750c9033c08aad10f4d5e13c0587e91
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,13 +2,13 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
factbase (0.0.0)
|
5
|
-
backtrace (
|
6
|
-
decoor (
|
5
|
+
backtrace (>= 0.4.0)
|
6
|
+
decoor (>= 0.0.1)
|
7
7
|
json (~> 2.7)
|
8
|
-
loog (
|
8
|
+
loog (>= 0.6.0)
|
9
9
|
nokogiri (~> 1.10)
|
10
|
-
others (
|
11
|
-
tago (
|
10
|
+
others (>= 0.0.3)
|
11
|
+
tago (>= 0.0.2)
|
12
12
|
yaml (~> 0.3)
|
13
13
|
|
14
14
|
GEM
|
data/README.md
CHANGED
@@ -207,30 +207,38 @@ 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
|
-
```
|
210
|
+
```text
|
211
|
+
|
212
|
+
|
213
|
+
Benchmarking, please wait a few seconds...
|
211
214
|
user system total real
|
212
|
-
insert
|
213
|
-
export
|
214
|
-
import
|
215
|
-
insert 10 facts 0.
|
216
|
-
query 10 times
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
(
|
221
|
-
(eq
|
222
|
-
(and (eq
|
223
|
-
(eq
|
224
|
-
(
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
215
|
+
insert 500 facts 0.017487 0.000000 0.017487 ( 0.017560)
|
216
|
+
export 500 facts 0.000517 0.000024 0.000541 ( 0.000542)
|
217
|
+
import 10456 bytes (500 facts) 0.000404 0.000000 0.000404 ( 0.000404)
|
218
|
+
insert 10 facts 0.001825 0.000000 0.001825 ( 0.001826)
|
219
|
+
query 10 times w/txn 0.049619 0.003999 0.053618 ( 0.053621)
|
220
|
+
query 10 times w/o txn 0.043671 0.000000 0.043671 ( 0.043673)
|
221
|
+
modify 10 attrs w/txn 0.030897 0.000975 0.031872 ( 0.031875)
|
222
|
+
delete 10 facts w/txn 0.012024 0.000013 0.012037 ( 0.012038)
|
223
|
+
(and (eq what 'issue-was-closed') (exists... -> 200 7.836740 0.018246 7.854986 ( 7.855478)
|
224
|
+
(and (eq what 'issue-was-closed') (exists... -> 200/txn 8.817858 0.014003 8.831861 ( 8.832345)
|
225
|
+
(and (eq what 'issue-was-closed') (exists... -> zero 10.051354 0.017001 10.068355 ( 10.069083)
|
226
|
+
(and (eq what 'issue-was-closed') (exists... -> zero/txn 10.667497 0.016002 10.683499 ( 10.684435)
|
227
|
+
(gt time '2024-03-23T03:21:43Z') 0.008827 0.000000 0.008827 ( 0.008827)
|
228
|
+
(gt cost 50) 0.009784 0.000002 0.009786 ( 0.009789)
|
229
|
+
(eq title 'Object Thinking 5000') 0.008519 0.000000 0.008519 ( 0.008523)
|
230
|
+
(and (eq foo 42.998) (or (gt bar 200) (absent zzz))) 0.013591 0.000000 0.013591 ( 0.013595)
|
231
|
+
(eq id (agg (always) (max id))) 0.021419 0.000000 0.021419 ( 0.021423)
|
232
|
+
(join "c<=cost,b<=bar" (eq id (agg (always) (max id)))) 0.141201 0.000000 0.141201 ( 0.141216)
|
233
|
+
delete! 0.004025 0.000000 0.004025 ( 0.004027)
|
234
|
+
Taped.append() x50000 0.038128 0.001997 0.040125 ( 0.040130)
|
235
|
+
Taped.each() x125 1.510154 0.001001 1.511155 ( 1.511313)
|
236
|
+
Taped.delete_if() x375 0.883123 0.000000 0.883123 ( 0.883229)
|
229
237
|
```
|
230
238
|
|
231
239
|
The results were calculated in [this GHA job][benchmark-gha]
|
232
|
-
on 2025-
|
240
|
+
on 2025-03-03 at 15:04,
|
233
241
|
on Linux with 4 CPUs.
|
234
242
|
<!-- benchmark_end -->
|
235
243
|
|
236
|
-
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/
|
244
|
+
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13633891920
|
data/benchmark/bench_factbase.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
require_relative '../lib/factbase'
|
7
7
|
|
8
8
|
def bench_factbase(bmk, fb)
|
9
|
-
total =
|
9
|
+
total = 500
|
10
10
|
bmk.report("insert #{total} facts") do
|
11
11
|
total.times do
|
12
12
|
fact = fb.insert
|
@@ -32,21 +32,26 @@ def bench_factbase(bmk, fb)
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
bmk.report("query #{actions} times") do
|
35
|
+
bmk.report("query #{actions} times w/txn") do
|
36
36
|
fb.txn do |fbt|
|
37
37
|
actions.times do |i|
|
38
|
-
fbt.query("(gt foo #{i})").each.to_a
|
38
|
+
fbt.query("(gt foo #{i})").each.to_a.each
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
|
-
bmk.report("
|
42
|
+
bmk.report("query #{actions} times w/o txn") do
|
43
|
+
actions.times do |i|
|
44
|
+
fb.query("(gt foo #{i})").each.to_a.each
|
45
|
+
end
|
46
|
+
end
|
47
|
+
bmk.report("modify #{actions} attrs w/txn") do
|
43
48
|
fb.txn do |fbt|
|
44
49
|
actions.times do |i|
|
45
50
|
fbt.query("(gt foo #{i})").each.to_a.first.bar = 55
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
49
|
-
bmk.report("delete #{actions} facts") do
|
54
|
+
bmk.report("delete #{actions} facts w/txn") do
|
50
55
|
fb.txn do |fbt|
|
51
56
|
actions.times do |i|
|
52
57
|
fbt.query("(gt foo #{100 - i})").delete!
|
@@ -0,0 +1,128 @@
|
|
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/logged'
|
8
|
+
|
9
|
+
def bench_large_query(bmk, fb)
|
10
|
+
total = 200
|
11
|
+
repo = 'foo'
|
12
|
+
total.times do |i|
|
13
|
+
f = fb.insert
|
14
|
+
f.id = i
|
15
|
+
f.where = 'github'
|
16
|
+
f.what = 'issue-was-closed'
|
17
|
+
f.who = 444
|
18
|
+
f.when = Time.now - (i * rand(1_000..100_000))
|
19
|
+
f.issue = i
|
20
|
+
f.repository = repo
|
21
|
+
end
|
22
|
+
total.times do |i|
|
23
|
+
f = fb.insert
|
24
|
+
f.id = i + total
|
25
|
+
f.where = 'github'
|
26
|
+
f.what = 'label-was-attached'
|
27
|
+
f.when = Time.now - (i * rand(1_000..100_000))
|
28
|
+
f.issue = i
|
29
|
+
f.repository = repo
|
30
|
+
f.label = 'bug'
|
31
|
+
end
|
32
|
+
total.times do |i|
|
33
|
+
f = fb.insert
|
34
|
+
f.id = i + (total * 2)
|
35
|
+
f.where = 'github'
|
36
|
+
f.what = 'issue-was-opened'
|
37
|
+
f.who = 555
|
38
|
+
f.when = Time.now - (i * rand(1_000..100_000))
|
39
|
+
f.issue = i
|
40
|
+
f.repository = repo
|
41
|
+
end
|
42
|
+
total.times do |i|
|
43
|
+
f = fb.insert
|
44
|
+
f.id = i + (total * 3)
|
45
|
+
f.where = 'github'
|
46
|
+
f.what = 'issue-was-assigned'
|
47
|
+
f.who = 666
|
48
|
+
f.when = Time.now - (i * rand(1_000..100_000))
|
49
|
+
f.issue = i
|
50
|
+
f.repository = repo
|
51
|
+
end
|
52
|
+
|
53
|
+
q = "(and
|
54
|
+
(eq what 'issue-was-closed')
|
55
|
+
(exists where)
|
56
|
+
(exists who)
|
57
|
+
(exists when)
|
58
|
+
(exists issue)
|
59
|
+
(exists repository)
|
60
|
+
(join 'label' (and
|
61
|
+
(eq what 'label-was-attached')
|
62
|
+
(eq issue $issue)
|
63
|
+
(eq where $where)
|
64
|
+
(eq repository $repository)
|
65
|
+
(or (eq label 'bug') (eq label 'enhancement') (eq label 'question'))))
|
66
|
+
(exists label)
|
67
|
+
(join 'opened_when<=when,opener<=who' (and
|
68
|
+
(eq what 'issue-was-opened')
|
69
|
+
(eq where $where)
|
70
|
+
(eq issue $issue)
|
71
|
+
(eq repository $repository)))
|
72
|
+
(exists opener)
|
73
|
+
(join 'assigned_when<=when,assigner<=who' (and
|
74
|
+
(eq what 'issue-was-assigned')
|
75
|
+
(eq where $where)
|
76
|
+
(eq issue $issue)
|
77
|
+
(eq repository $repository)))
|
78
|
+
(exists assigner)
|
79
|
+
(as seconds (to_integer (minus when assigned_when)))
|
80
|
+
(as closer who)
|
81
|
+
(as who assigner)
|
82
|
+
(empty (and
|
83
|
+
(eq what 'bug-was-resolved')
|
84
|
+
(eq where $where)
|
85
|
+
(eq issue $issue)
|
86
|
+
(eq repository $repository))))".gsub(/\s+/, ' ')
|
87
|
+
|
88
|
+
cycles = 1
|
89
|
+
bmk.report("#{q[0..40]}... -> #{total}") do
|
90
|
+
cycles.times do
|
91
|
+
t = fb.query(q).each.to_a.size
|
92
|
+
raise "Found #{t} facts, expected to find #{total}" unless t == total
|
93
|
+
end
|
94
|
+
end
|
95
|
+
bmk.report("#{q[0..40]}... -> #{total}/txn") do
|
96
|
+
cycles.times do
|
97
|
+
fb.txn do |fbt|
|
98
|
+
t = fbt.query(q).each.to_a.size
|
99
|
+
raise "Found #{t} facts, expected to find #{total}" unless t == total
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
total.times do |i|
|
105
|
+
f = fb.insert
|
106
|
+
f.id = i
|
107
|
+
f.where = 'github'
|
108
|
+
f.what = 'bug-was-resolved'
|
109
|
+
f.who = 444
|
110
|
+
f.when = Time.now - (i * rand(1_000..100_000))
|
111
|
+
f.issue = i
|
112
|
+
f.repository = repo
|
113
|
+
end
|
114
|
+
bmk.report("#{q[0..40]}... -> zero") do
|
115
|
+
cycles.times do
|
116
|
+
t = fb.query(q).each.to_a.size
|
117
|
+
raise "Found #{t} facts, expected to find nothing" unless t.zero?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
bmk.report("#{q[0..40]}... -> zero/txn") do
|
121
|
+
cycles.times do
|
122
|
+
fb.txn do |fbt|
|
123
|
+
t = fbt.query(q).each.to_a.size
|
124
|
+
raise "Found #{t} facts, expected to find nothing" unless t.zero?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/benchmark/bench_query.rb
CHANGED
data/factbase.gemspec
CHANGED
@@ -25,13 +25,13 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.files = `git ls-files`.split($RS)
|
26
26
|
s.rdoc_options = ['--charset=UTF-8']
|
27
27
|
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
28
|
-
s.add_dependency 'backtrace', '
|
29
|
-
s.add_dependency 'decoor', '
|
28
|
+
s.add_dependency 'backtrace', '>=0.4.0'
|
29
|
+
s.add_dependency 'decoor', '>=0.0.1'
|
30
30
|
s.add_dependency 'json', '~>2.7'
|
31
|
-
s.add_dependency 'loog', '
|
31
|
+
s.add_dependency 'loog', '>=0.6.0'
|
32
32
|
s.add_dependency 'nokogiri', '~>1.10'
|
33
|
-
s.add_dependency 'others', '
|
34
|
-
s.add_dependency 'tago', '
|
33
|
+
s.add_dependency 'others', '>=0.0.3'
|
34
|
+
s.add_dependency 'tago', '>=0.0.2'
|
35
35
|
s.add_dependency 'yaml', '~>0.3'
|
36
36
|
s.metadata['rubygems_mfa_required'] = 'true'
|
37
37
|
end
|
data/lib/factbase/accum.rb
CHANGED
@@ -41,7 +41,7 @@ class Factbase::Accum
|
|
41
41
|
kk = args[1].to_s
|
42
42
|
vv = @props[kk].nil? ? [] : @props[kk]
|
43
43
|
vvv = @fact.method_missing(*args)
|
44
|
-
vvv = [vvv] unless vvv.nil? || vvv.respond_to?(:
|
44
|
+
vvv = [vvv] unless vvv.nil? || vvv.respond_to?(:to_a)
|
45
45
|
vv += vvv.to_a unless vvv.nil?
|
46
46
|
vv.uniq!
|
47
47
|
vv.empty? ? nil : vv
|
data/lib/factbase/taped.rb
CHANGED
@@ -68,7 +68,7 @@ class Factbase::Taped
|
|
68
68
|
|
69
69
|
def [](key)
|
70
70
|
v = @origin[key]
|
71
|
-
v = TapedArray.new(v, @origin.object_id, @added) if v.
|
71
|
+
v = TapedArray.new(v, @origin.object_id, @added) if v.is_a?(Array)
|
72
72
|
v
|
73
73
|
end
|
74
74
|
|
@@ -87,6 +87,7 @@ class Factbase::Taped
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def each(&)
|
90
|
+
return to_enum(__method__) unless block_given?
|
90
91
|
@origin.each(&)
|
91
92
|
end
|
92
93
|
|
data/lib/factbase/term.rb
CHANGED
@@ -172,12 +172,20 @@ class Factbase::Term
|
|
172
172
|
fact[k]
|
173
173
|
end
|
174
174
|
|
175
|
+
# @return [Array|nil] Either array of values or NIL
|
175
176
|
def the_values(pos, fact, maps)
|
176
177
|
v = @operands[pos]
|
177
178
|
v = v.evaluate(fact, maps) if v.is_a?(Factbase::Term)
|
178
179
|
v = fact[v.to_s] if v.is_a?(Symbol)
|
179
180
|
return v if v.nil?
|
180
|
-
|
181
|
+
unless v.is_a?(Array)
|
182
|
+
v =
|
183
|
+
if v.respond_to?(:each)
|
184
|
+
v.to_a
|
185
|
+
else
|
186
|
+
[v]
|
187
|
+
end
|
188
|
+
end
|
181
189
|
v
|
182
190
|
end
|
183
191
|
end
|
@@ -50,7 +50,7 @@ module Factbase::Term::Aggregates
|
|
50
50
|
maps.each do |m|
|
51
51
|
vv = m[k.to_s]
|
52
52
|
next if vv.nil?
|
53
|
-
vv = [vv] unless vv.respond_to?(:
|
53
|
+
vv = [vv] unless vv.respond_to?(:to_a)
|
54
54
|
vv.each do |v|
|
55
55
|
sum += v
|
56
56
|
end
|
@@ -82,7 +82,7 @@ module Factbase::Term::Aggregates
|
|
82
82
|
maps.each do |m|
|
83
83
|
vv = m[k.to_s]
|
84
84
|
next if vv.nil?
|
85
|
-
vv = [vv] unless vv.respond_to?(:
|
85
|
+
vv = [vv] unless vv.respond_to?(:to_a)
|
86
86
|
vv.each do |v|
|
87
87
|
best = v if best.nil? || yield(v, best)
|
88
88
|
end
|
data/lib/factbase/terms/meta.rb
CHANGED
@@ -24,7 +24,7 @@ module Factbase::Term::Ordering
|
|
24
24
|
assert_args(1)
|
25
25
|
vv = the_values(0, fact, maps)
|
26
26
|
return false if vv.nil?
|
27
|
-
vv = [vv] unless vv.respond_to?(:
|
27
|
+
vv = [vv] unless vv.respond_to?(:to_a)
|
28
28
|
vv.each do |v|
|
29
29
|
return false if @uniques.include?(v)
|
30
30
|
@uniques << v
|
data/lib/factbase.rb
CHANGED
@@ -64,7 +64,7 @@ require 'yaml'
|
|
64
64
|
# License:: MIT
|
65
65
|
class Factbase
|
66
66
|
# Current version of the gem (changed by .rultor.yml on every release)
|
67
|
-
VERSION = '0.7.
|
67
|
+
VERSION = '0.7.5'
|
68
68
|
|
69
69
|
# An exception that may be thrown in a transaction, to roll it back.
|
70
70
|
class Rollback < StandardError; end
|
metadata
CHANGED
@@ -1,42 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: factbase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: backtrace
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
15
15
|
requirements:
|
16
|
-
- - "
|
16
|
+
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version:
|
18
|
+
version: 0.4.0
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
|
-
- - "
|
23
|
+
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version:
|
25
|
+
version: 0.4.0
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: decoor
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
|
-
- - "
|
30
|
+
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 0.0.1
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: 0.0.1
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: json
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,16 +55,16 @@ dependencies:
|
|
55
55
|
name: loog
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
|
-
- - "
|
58
|
+
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 0.6.0
|
61
61
|
type: :runtime
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "
|
65
|
+
- - ">="
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
67
|
+
version: 0.6.0
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
69
|
name: nokogiri
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,30 +83,30 @@ dependencies:
|
|
83
83
|
name: others
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
|
-
- - "
|
86
|
+
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
88
|
+
version: 0.0.3
|
89
89
|
type: :runtime
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
|
-
- - "
|
93
|
+
- - ">="
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
95
|
+
version: 0.0.3
|
96
96
|
- !ruby/object:Gem::Dependency
|
97
97
|
name: tago
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
|
-
- - "
|
100
|
+
- - ">="
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
102
|
+
version: 0.0.2
|
103
103
|
type: :runtime
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- - "
|
107
|
+
- - ">="
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 0.0.2
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: yaml
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- REUSE.toml
|
159
159
|
- Rakefile
|
160
160
|
- benchmark/bench_factbase.rb
|
161
|
+
- benchmark/bench_large_query.rb
|
161
162
|
- benchmark/bench_query.rb
|
162
163
|
- benchmark/bench_taped.rb
|
163
164
|
- factbase.gemspec
|
@@ -169,7 +170,6 @@ files:
|
|
169
170
|
- lib/factbase/inv.rb
|
170
171
|
- lib/factbase/light.rb
|
171
172
|
- lib/factbase/logged.rb
|
172
|
-
- lib/factbase/looged.rb
|
173
173
|
- lib/factbase/pre.rb
|
174
174
|
- lib/factbase/query.rb
|
175
175
|
- lib/factbase/query_once.rb
|
@@ -212,7 +212,6 @@ files:
|
|
212
212
|
- test/factbase/test_flatten.rb
|
213
213
|
- test/factbase/test_inv.rb
|
214
214
|
- test/factbase/test_logged.rb
|
215
|
-
- test/factbase/test_looged.rb
|
216
215
|
- test/factbase/test_pre.rb
|
217
216
|
- test/factbase/test_query.rb
|
218
217
|
- test/factbase/test_rules.rb
|
data/lib/factbase/looged.rb
DELETED
@@ -1,172 +0,0 @@
|
|
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::Looged
|
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::Looged.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::Looged.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::Looged.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::Looged.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::Looged.elapsed do
|
153
|
-
r = @fb.query(@expr).delete!
|
154
|
-
end
|
155
|
-
raise ".delete! of #{@query.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
|
@@ -1,129 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
4
|
-
# SPDX-License-Identifier: MIT
|
5
|
-
|
6
|
-
require_relative '../test__helper'
|
7
|
-
require 'loog'
|
8
|
-
require_relative '../../lib/factbase'
|
9
|
-
require_relative '../../lib/factbase/looged'
|
10
|
-
|
11
|
-
# Test.
|
12
|
-
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
13
|
-
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
14
|
-
# License:: MIT
|
15
|
-
class TestLooged < Factbase::Test
|
16
|
-
def test_simple_setting
|
17
|
-
fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
|
18
|
-
fb.insert
|
19
|
-
fb.insert.bar = 3
|
20
|
-
found = 0
|
21
|
-
fb.query('(exists bar)').each do |f|
|
22
|
-
assert_predicate(f.bar, :positive?)
|
23
|
-
f.foo = 42
|
24
|
-
assert_equal(42, f.foo)
|
25
|
-
found += 1
|
26
|
-
end
|
27
|
-
assert_equal(1, found)
|
28
|
-
assert_equal(2, fb.size)
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_reading_one
|
32
|
-
fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
|
33
|
-
fb.insert
|
34
|
-
fb.insert.bar = 42
|
35
|
-
assert_equal(1, fb.query('(agg (exists bar) (count))').one)
|
36
|
-
assert_equal([42], fb.query('(agg (exists bar) (first bar))').one)
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_with_txn
|
40
|
-
log = Loog::Buffer.new
|
41
|
-
fb = Factbase::Looged.new(Factbase.new, log)
|
42
|
-
assert(
|
43
|
-
fb.txn do |fbt|
|
44
|
-
fbt.insert.foo = 42
|
45
|
-
end
|
46
|
-
)
|
47
|
-
assert_equal(1, fb.size)
|
48
|
-
assert_includes(log.to_s, 'touched', log)
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_with_txn_rollback
|
52
|
-
log = Loog::Buffer.new
|
53
|
-
fb = Factbase::Looged.new(Factbase.new, log)
|
54
|
-
assert_equal(0, fb.txn { raise Factbase::Rollback })
|
55
|
-
assert_equal(0, fb.size)
|
56
|
-
assert_includes(log.to_s, 'rolled back', log)
|
57
|
-
refute_includes(log.to_s, 'didn\'t touch', log)
|
58
|
-
end
|
59
|
-
|
60
|
-
def test_with_modifying_txn
|
61
|
-
log = Loog::Buffer.new
|
62
|
-
fb = Factbase::Looged.new(Factbase.new, log)
|
63
|
-
fb.insert.foo = 1
|
64
|
-
assert_equal(0, fb.txn { |fbt| fbt.query('(always)').each.to_a }.to_i, log)
|
65
|
-
assert_equal(1, fb.txn { |fbt| fbt.query('(always)').each.to_a[0].foo = 42 }.to_i)
|
66
|
-
assert_includes(log.to_s, 'touched', log)
|
67
|
-
end
|
68
|
-
|
69
|
-
def test_with_empty_txn
|
70
|
-
log = Loog::Buffer.new
|
71
|
-
fb = Factbase::Looged.new(Factbase.new, log)
|
72
|
-
assert_equal(0, fb.txn { |fbt| fbt.query('(always)').each.to_a }.to_i)
|
73
|
-
assert_includes(log.to_s, 'touched', log)
|
74
|
-
end
|
75
|
-
|
76
|
-
def test_returns_int
|
77
|
-
fb = Factbase.new
|
78
|
-
fb.insert
|
79
|
-
fb.insert
|
80
|
-
assert_equal(2, Factbase::Looged.new(fb, Loog::NULL).query('(always)').each(&:to_s))
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_returns_int_when_empty
|
84
|
-
fb = Factbase.new
|
85
|
-
assert_equal(0, Factbase::Looged.new(fb, Loog::NULL).query('(always)').each(&:to_s))
|
86
|
-
end
|
87
|
-
|
88
|
-
def test_logs_when_enumerator
|
89
|
-
fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
|
90
|
-
assert_equal(0, fb.query('(always)').each.to_a.size)
|
91
|
-
fb.insert
|
92
|
-
assert_equal(1, fb.query('(always)').each.to_a.size)
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_proper_logging
|
96
|
-
log = Loog::Buffer.new
|
97
|
-
fb = Factbase::Looged.new(Factbase.new, log)
|
98
|
-
fb.insert
|
99
|
-
fb.insert.bar = 3
|
100
|
-
fb.insert
|
101
|
-
fb.insert.str =
|
102
|
-
"Он поскорей звонит. Вбегает
|
103
|
-
К нему слуга француз Гильо,
|
104
|
-
Халат и туфли предлагает
|
105
|
-
И подает ему белье.
|
106
|
-
Спешит Онегин одеваться,
|
107
|
-
Слуге велит приготовляться
|
108
|
-
С ним вместе ехать и с собой
|
109
|
-
Взять также ящик боевой.
|
110
|
-
Готовы санки беговые.
|
111
|
-
Он сел, на мельницу летит.
|
112
|
-
Примчались. Он слуге велит
|
113
|
-
Лепажа стволы роковые
|
114
|
-
Нести за ним, а лошадям
|
115
|
-
Отъехать в поле к двум дубкам."
|
116
|
-
fb.query('(exists bar)').each(&:to_s)
|
117
|
-
fb.query('(not (exists bar))').delete!
|
118
|
-
[
|
119
|
-
'Inserted new fact #1',
|
120
|
-
'Inserted new fact #2',
|
121
|
-
'Set \'bar\' to 3 (Integer)',
|
122
|
-
'Set \'str\' to "Он поскорей звонит. Вбегает\n ... Отъехать в поле к двум дубкам." (String)',
|
123
|
-
'Found 1 fact(s) by \'(exists bar)\'',
|
124
|
-
'Deleted 3 fact(s) out of 4 by \'(not (exists bar))\''
|
125
|
-
].each do |s|
|
126
|
-
assert_includes(log.to_s, s, "#{log}\n")
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|