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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20e78ab8a1e9e32a777880d99f15cb339c22310b152fcecc626847a364536059
4
- data.tar.gz: 480617fd3b19baa46c43553eb6eb87accc4bd4faa8c28a7d121803b1e5f34da0
3
+ metadata.gz: 7a3adc3cc2aec38609bd1d98b4266e18db6d3fc9e0e9eb3714f9772bf6bfee60
4
+ data.tar.gz: 7eae0cf3bbd8b1d432971f051ad08be1f39df640245d2b0f565c14a95a436aa1
5
5
  SHA512:
6
- metadata.gz: dd1fcc7943c1c3c0cc0325f1dbbfeac4be919f79d7b456ea9a9a2ffae2548dac88825854c77d8de5ca91bfbc135fd79e227c745a7793cc27a455ef9a0262367d
7
- data.tar.gz: c4e4f3faf564894f4e57e308fb768906ab59a3e413fb9e45f6f9674e183382d5b7c1616465e6a2ba6382f0a8950ea56a1cef423cbfa662511a3dfded178ff4d1
6
+ metadata.gz: ee55ea6c2b5c2bd780c5b241bc16c14a2151adcb44829ec15b71c6e6fa614f9c9776e11d7c0bf76e2693a9141c745a0a233946e7b09b9961dc042f2ab22d412a
7
+ data.tar.gz: 59c44e45b9a6f2bbfe70a7fe1e649fba6c3af8b9d04e9d225289cd32d7736db1e37a138f53ed300b5639fdadbe9cef448750c9033c08aad10f4d5e13c0587e91
@@ -27,7 +27,7 @@ jobs:
27
27
  - run: |
28
28
  set -x
29
29
  sum=$(
30
- echo '```'
30
+ echo '```text'
31
31
  cat stdout.txt
32
32
  echo '```'
33
33
  echo
data/.gitignore CHANGED
@@ -5,5 +5,6 @@
5
5
  .yardoc/
6
6
  coverage/
7
7
  doc/
8
+ node_modules/
8
9
  rdoc/
9
10
  vendor/
data/Gemfile.lock CHANGED
@@ -2,13 +2,13 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  factbase (0.0.0)
5
- backtrace (> 0)
6
- decoor (> 0)
5
+ backtrace (>= 0.4.0)
6
+ decoor (>= 0.0.1)
7
7
  json (~> 2.7)
8
- loog (> 0)
8
+ loog (>= 0.6.0)
9
9
  nokogiri (~> 1.10)
10
- others (> 0)
11
- tago (> 0)
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 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)
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-02-28 at 09:13,
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/13584960859
244
+ [benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/13633891920
@@ -6,7 +6,7 @@
6
6
  require_relative '../lib/factbase'
7
7
 
8
8
  def bench_factbase(bmk, fb)
9
- total = 50_000
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("modify #{actions} attrs") do
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
@@ -6,7 +6,7 @@
6
6
  require_relative '../lib/factbase'
7
7
 
8
8
  def bench_query(bmk, fb)
9
- total = 5_000
9
+ total = 500
10
10
  total.times do |i|
11
11
  f = fb.insert
12
12
  f.id = i
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', '>0'
29
- s.add_dependency 'decoor', '>0'
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', '>0'
31
+ s.add_dependency 'loog', '>=0.6.0'
32
32
  s.add_dependency 'nokogiri', '~>1.10'
33
- s.add_dependency 'others', '>0'
34
- s.add_dependency 'tago', '>0'
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
@@ -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?(:each)
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
@@ -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.respond_to?(:each)
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
- v = [v] unless v.respond_to?(:each)
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?(:each)
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?(:each)
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
@@ -25,7 +25,7 @@ module Factbase::Term::Meta
25
25
  assert_args(1)
26
26
  v = by_symbol(0, fact)
27
27
  return 0 if v.nil?
28
- return 1 unless v.respond_to?(:each)
28
+ return 1 unless v.respond_to?(:to_a)
29
29
  v.size
30
30
  end
31
31
 
@@ -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?(:each)
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.4'
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
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-02-28 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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
@@ -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