factbase 0.8.0 → 0.9.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +13 -13
  5. data/README.md +24 -24
  6. data/REUSE.toml +7 -2
  7. data/Rakefile +8 -1
  8. data/benchmark/bench_factbase.rb +1 -1
  9. data/fixtures/stories/agg.yml +17 -0
  10. data/fixtures/stories/always.yml +16 -0
  11. data/fixtures/stories/as.yml +16 -0
  12. data/fixtures/stories/count.yml +18 -0
  13. data/fixtures/stories/eq.yml +30 -0
  14. data/fixtures/stories/gt.yml +18 -0
  15. data/fixtures/stories/join.yml +19 -0
  16. data/fixtures/stories/max.yml +14 -0
  17. data/fixtures/stories/min.yml +14 -0
  18. data/fixtures/stories/nth.yml +14 -0
  19. data/fixtures/stories/or.yml +18 -0
  20. data/fixtures/stories/sprintf.yml +12 -0
  21. data/fixtures/stories/sum.yml +14 -0
  22. data/lib/factbase/cached/cached_fact.rb +28 -0
  23. data/lib/factbase/cached/cached_factbase.rb +64 -0
  24. data/lib/factbase/cached/cached_query.rb +61 -0
  25. data/lib/factbase/cached/cached_term.rb +25 -0
  26. data/lib/factbase/fact.rb +13 -13
  27. data/lib/factbase/indexed/indexed_fact.rb +28 -0
  28. data/lib/factbase/indexed/indexed_factbase.rb +64 -0
  29. data/lib/factbase/indexed/indexed_query.rb +56 -0
  30. data/lib/factbase/indexed/indexed_term.rb +60 -0
  31. data/lib/factbase/inv.rb +18 -6
  32. data/lib/factbase/light.rb +7 -6
  33. data/lib/factbase/logged.rb +87 -62
  34. data/lib/factbase/query.rb +29 -34
  35. data/lib/factbase/rules.rb +16 -15
  36. data/lib/factbase/sync/sync_factbase.rb +57 -0
  37. data/lib/factbase/sync/sync_query.rb +61 -0
  38. data/lib/factbase/syntax.rb +12 -25
  39. data/lib/factbase/tallied.rb +11 -10
  40. data/lib/factbase/taped.rb +8 -0
  41. data/lib/factbase/tee.rb +2 -0
  42. data/lib/factbase/term.rb +45 -17
  43. data/lib/factbase/terms/aggregates.rb +17 -15
  44. data/lib/factbase/terms/aliases.rb +4 -4
  45. data/lib/factbase/terms/casting.rb +8 -8
  46. data/lib/factbase/terms/debug.rb +2 -2
  47. data/lib/factbase/terms/defn.rb +3 -3
  48. data/lib/factbase/terms/logical.rb +53 -14
  49. data/lib/factbase/terms/math.rb +26 -26
  50. data/lib/factbase/terms/meta.rb +14 -14
  51. data/lib/factbase/terms/ordering.rb +4 -4
  52. data/lib/factbase/terms/strings.rb +8 -8
  53. data/lib/factbase/terms/system.rb +3 -3
  54. data/lib/factbase.rb +67 -55
  55. data/test/factbase/cached/test_cached_factbase.rb +22 -0
  56. data/test/factbase/cached/test_cached_query.rb +79 -0
  57. data/test/factbase/indexed/test_indexed_query.rb +175 -0
  58. data/test/factbase/sync/test_sync_query.rb +30 -0
  59. data/test/factbase/terms/test_aggregates.rb +5 -5
  60. data/test/factbase/terms/test_aliases.rb +7 -7
  61. data/test/factbase/terms/test_casting.rb +8 -8
  62. data/test/factbase/terms/test_debug.rb +6 -6
  63. data/test/factbase/terms/test_defn.rb +14 -14
  64. data/test/factbase/terms/test_logical.rb +17 -19
  65. data/test/factbase/terms/test_math.rb +63 -61
  66. data/test/factbase/terms/test_meta.rb +36 -36
  67. data/test/factbase/terms/test_ordering.rb +9 -9
  68. data/test/factbase/terms/test_strings.rb +10 -10
  69. data/test/factbase/terms/test_system.rb +6 -6
  70. data/test/factbase/test_accum.rb +5 -5
  71. data/test/factbase/test_fact.rb +12 -12
  72. data/test/factbase/test_logged.rb +7 -0
  73. data/test/factbase/test_query.rb +110 -37
  74. data/test/factbase/test_rules.rb +1 -1
  75. data/test/factbase/test_syntax.rb +12 -12
  76. data/test/factbase/test_tee.rb +8 -8
  77. data/test/factbase/test_term.rb +39 -30
  78. data/test/test__helper.rb +2 -2
  79. data/test/test_factbase.rb +6 -0
  80. metadata +29 -4
  81. data/lib/factbase/query_once.rb +0 -54
  82. data/lib/factbase/term_once.rb +0 -67
@@ -4,19 +4,65 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require_relative '../test__helper'
7
+ require 'loog'
7
8
  require 'time'
8
9
  require_relative '../../lib/factbase'
9
10
  require_relative '../../lib/factbase/query'
11
+ require_relative '../../lib/factbase/logged'
12
+ require_relative '../../lib/factbase/pre'
13
+ require_relative '../../lib/factbase/inv'
14
+ require_relative '../../lib/factbase/rules'
15
+ require_relative '../../lib/factbase/tallied'
16
+ require_relative '../../lib/factbase/cached/cached_factbase'
17
+ require_relative '../../lib/factbase/indexed/indexed_factbase'
18
+ require_relative '../../lib/factbase/sync/sync_factbase'
10
19
 
11
20
  # Query test.
12
21
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
22
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
14
23
  # License:: MIT
15
24
  class TestQuery < Factbase::Test
25
+ def test_stories
26
+ with_factbases do |badge, fb|
27
+ Dir[File.join(__dir__, '../../fixtures/stories/**/*.yml')].each do |fixture|
28
+ base = File.basename(fixture)
29
+ story = YAML.load_file(fixture)
30
+ 2.times do
31
+ fb.query('(always)').delete!
32
+ story['facts'].each do |y|
33
+ f = fb.insert
34
+ y.each do |k, vv|
35
+ vv = [vv] unless vv.is_a?(Array)
36
+ vv.each do |v|
37
+ f.send(:"#{k}=", v)
38
+ end
39
+ end
40
+ end
41
+ story['queries'].each do |q|
42
+ qry = q['query']
43
+ if q['size']
44
+ size = q['size']
45
+ assert_equal(size, fb.query(qry).each.to_a.size, "#{base}: #{qry} at #{badge}")
46
+ fb.txn do |fbt|
47
+ assert_equal(size, fbt.query(qry).each.to_a.size, "#{base}: #{qry} at #{badge} (in txn)")
48
+ end
49
+ else
50
+ ret = q['one']
51
+ assert_equal(ret, fb.query(qry).one, "#{base}: #{qry} at #{badge}")
52
+ fb.txn do |fbt|
53
+ assert_equal(ret, fbt.query(qry).one, "#{base}: #{qry} at #{badge} (in txn)")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
16
62
  def test_simple_parsing
17
63
  maps = []
18
64
  maps << { 'foo' => [42] }
19
- q = Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo 42)')
65
+ q = Factbase::Query.new(maps, '(eq foo 42)', Factbase.new)
20
66
  assert_equal(
21
67
  1,
22
68
  q.each do |f|
@@ -26,12 +72,7 @@ class TestQuery < Factbase::Test
26
72
  end
27
73
 
28
74
  def test_complex_parsing
29
- maps = [
30
- { 'num' => [42], 'name' => ['Jeff'] },
31
- { 'pi' => [3.14], 'num' => [42, 66, 0], 'name' => ['peter'] },
32
- { 'time' => [Time.now - 100], 'num' => [0], 'hi' => [4], 'nome' => ['Walter'] }
33
- ]
34
- {
75
+ queries = {
35
76
  '(eq num 444)' => 0,
36
77
  '(eq hi 4)' => 1,
37
78
  '(eq time 0)' => 0,
@@ -71,11 +112,18 @@ class TestQuery < Factbase::Test
71
112
  '(undef something)' => 3,
72
113
  "(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1,
73
114
  '(eq 3 (agg (eq num $num) (count)))' => 1
74
- }.each do |q, r|
75
- fb = Factbase.new(maps)
76
- assert_equal(r, fb.query(q).each.to_a.size, q)
77
- fb.txn do |fbt|
78
- assert_equal(r, fbt.query(q).each.to_a.size, q)
115
+ }
116
+ maps = [
117
+ { 'num' => [42], 'name' => ['Jeff'] },
118
+ { 'num' => [42, 66, 0], 'pi' => [3.14], 'name' => ['peter'] },
119
+ { 'num' => [0], 'time' => [Time.now - 100], 'hi' => [4], 'nome' => ['Walter'] }
120
+ ]
121
+ with_factbases(maps) do |badge, fb|
122
+ queries.each do |q, r|
123
+ assert_equal(r, fb.query(q).each.to_a.size, "#{q} in #{badge}")
124
+ fb.txn do |fbt|
125
+ assert_equal(r, fbt.query(q).each.to_a.size, "#{q} in #{badge} (txn)")
126
+ end
79
127
  end
80
128
  end
81
129
  end
@@ -84,7 +132,7 @@ class TestQuery < Factbase::Test
84
132
  maps = []
85
133
  now = Time.now.utc
86
134
  maps << { 'foo' => [now] }
87
- q = Factbase::Query.new(Factbase.new, maps, Mutex.new, "(eq foo #{now.iso8601})")
135
+ q = Factbase::Query.new(maps, "(eq foo #{now.iso8601})", Factbase.new)
88
136
  assert_equal(1, q.each.to_a.size)
89
137
  end
90
138
 
@@ -94,7 +142,7 @@ class TestQuery < Factbase::Test
94
142
  { 'bar' => [4, 5] },
95
143
  { 'bar' => [5] }
96
144
  ]
97
- q = Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq bar 5)')
145
+ q = Factbase::Query.new(maps, '(eq bar 5)', Factbase.new)
98
146
  assert_equal(2, q.delete!)
99
147
  assert_equal(1, maps.size)
100
148
  end
@@ -104,18 +152,20 @@ class TestQuery < Factbase::Test
104
152
  { 'foo' => [42] },
105
153
  { 'bar' => [4, 5] }
106
154
  ]
107
- {
108
- '(agg (exists foo) (first foo))' => [42],
109
- '(agg (exists z) (first z))' => nil,
110
- '(agg (always) (count))' => 2,
111
- '(agg (eq bar $v) (count))' => 1,
112
- '(agg (eq z 40) (count))' => 0
113
- }.each do |q, expected|
114
- result = Factbase::Query.new(Factbase.new, maps, Mutex.new, q).one(v: 4)
115
- if expected.nil?
116
- assert_nil(result, "#{q} -> nil")
117
- else
118
- assert_equal(expected, result, "#{q} -> #{expected}")
155
+ with_factbases(maps) do |badge, fb|
156
+ {
157
+ '(agg (exists foo) (first foo))' => [42],
158
+ '(agg (exists z) (first z))' => nil,
159
+ '(agg (always) (count))' => 2,
160
+ '(agg (eq bar $v) (count))' => 1,
161
+ '(agg (eq z 40) (count))' => 0
162
+ }.each do |q, expected|
163
+ result = fb.query(q).one(fb, v: 4)
164
+ if expected.nil?
165
+ assert_nil(result, "#{q} -> nil in #{badge}")
166
+ else
167
+ assert_equal(expected, result, "#{q} -> #{expected} in #{badge}")
168
+ end
119
169
  end
120
170
  end
121
171
  end
@@ -126,28 +176,30 @@ class TestQuery < Factbase::Test
126
176
  { 'bar' => [4, 5] },
127
177
  { 'bar' => [5] }
128
178
  ]
129
- q = Factbase::Query.new(Factbase.new, maps, Mutex.new, '(never)')
130
- assert_equal(0, q.delete!)
131
- assert_equal(3, maps.size)
179
+ with_factbases(maps) do |badge, fb|
180
+ q = fb.query('(never)')
181
+ assert_equal(0, q.delete!, "#{q} in #{badge}")
182
+ assert_equal(3, maps.size, "#{q} in #{badge}")
183
+ end
132
184
  end
133
185
 
134
186
  def test_to_array
135
187
  maps = []
136
188
  maps << { 'foo' => [42] }
137
- assert_equal(1, Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo 42)').each.to_a.size)
189
+ assert_equal(1, Factbase::Query.new(maps, '(eq foo 42)', Factbase.new).each.to_a.size)
138
190
  end
139
191
 
140
192
  def test_returns_int
141
193
  maps = []
142
194
  maps << { 'foo' => [1] }
143
- q = Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo 1)')
195
+ q = Factbase::Query.new(maps, '(eq foo 1)', Factbase.new)
144
196
  assert_equal(1, q.each(&:to_s))
145
197
  end
146
198
 
147
199
  def test_with_aliases
148
200
  maps = []
149
201
  maps << { 'foo' => [42] }
150
- assert_equal(45, Factbase::Query.new(Factbase.new, maps, Mutex.new, '(as bar (plus foo 3))').each.to_a[0].bar)
202
+ assert_equal(45, Factbase::Query.new(maps, '(as bar (plus foo 3))', Factbase.new).each.to_a[0].bar)
151
203
  assert_equal(1, maps[0].size)
152
204
  end
153
205
 
@@ -157,22 +209,43 @@ class TestQuery < Factbase::Test
157
209
  { 'foo' => [17] }
158
210
  ]
159
211
  found = 0
160
- Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo $bar)').each(bar: [42]) do
212
+ Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: [42]) do
161
213
  found += 1
162
214
  end
163
215
  assert_equal(1, found)
164
- assert_equal(1, Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo $bar)').each(bar: 42).to_a.size)
165
- assert_equal(0, Factbase::Query.new(Factbase.new, maps, Mutex.new, '(eq foo $bar)').each(bar: 555).to_a.size)
216
+ assert_equal(1, Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: 42).to_a.size)
217
+ assert_equal(0, Factbase::Query.new(maps, '(eq foo $bar)', Factbase.new).each(Factbase.new, bar: 555).to_a.size)
166
218
  end
167
219
 
168
220
  def test_with_nil_alias
169
221
  maps = [{ 'foo' => [42] }]
170
- assert_nil(Factbase::Query.new(Factbase.new, maps, Mutex.new, '(as bar (plus xxx 3))').each.to_a[0]['bar'])
222
+ assert_nil(Factbase::Query.new(maps, '(as bar (plus xxx 3))', Factbase.new).each.to_a[0]['bar'])
171
223
  end
172
224
 
173
225
  def test_get_all_properties
174
226
  maps = [{ 'foo' => [42] }]
175
- f = Factbase::Query.new(Factbase.new, maps, Mutex.new, '(always)').each.to_a[0]
227
+ f = Factbase::Query.new(maps, '(always)', Factbase.new).each.to_a[0]
176
228
  assert_includes(f.all_properties, 'foo')
177
229
  end
230
+
231
+ private
232
+
233
+ def with_factbases(maps = [], &)
234
+ {
235
+ 'plain' => Factbase.new(maps),
236
+ 'pre+plain' => Factbase::Pre.new(Factbase.new(maps)) { nil },
237
+ 'rules+plain' => Factbase::Rules.new(Factbase.new(maps), '(always)'),
238
+ 'inv+plain' => Factbase::Inv.new(Factbase.new(maps)) { nil },
239
+ 'sync+plain' => Factbase::SyncFactbase.new(Factbase.new(maps)),
240
+ 'tallied+plain' => Factbase::Tallied.new(Factbase.new(maps)),
241
+ 'indexed+plain' => Factbase::IndexedFactbase.new(Factbase.new(maps)),
242
+ 'cached+plain' => Factbase::CachedFactbase.new(Factbase.new(maps)),
243
+ 'logged+plain' => Factbase::Logged.new(Factbase.new(maps), Loog::NULL),
244
+ 'indexed+cached+plain' => Factbase::IndexedFactbase.new(Factbase::CachedFactbase.new(Factbase.new(maps))),
245
+ 'cached+indexed+plain' => Factbase::CachedFactbase.new(Factbase::IndexedFactbase.new(Factbase.new(maps))),
246
+ 'sync+cached+indexed+plain' => Factbase::SyncFactbase.new(
247
+ Factbase::CachedFactbase.new(Factbase::IndexedFactbase.new(Factbase.new(maps)))
248
+ )
249
+ }.each(&)
250
+ end
178
251
  end
@@ -51,7 +51,7 @@ class TestRules < Factbase::Test
51
51
  fb = Factbase::Rules.new(Factbase.new, '(always)')
52
52
  f = fb.insert
53
53
  f.foo = 42
54
- assert_equal(1, fb.query('(agg (eq foo $v) (count))').one(v: 42))
54
+ assert_equal(1, fb.query('(agg (eq foo $v) (count))').one(Factbase.new, v: 42))
55
55
  end
56
56
 
57
57
  def test_check_only_when_txn_is_closed
@@ -18,7 +18,7 @@ class TestSyntax < Factbase::Test
18
18
  "(foo 'one two three ')",
19
19
  "(foo 'one two three ' 'tail tail')"
20
20
  ].each do |q|
21
- assert_equal(q, Factbase::Syntax.new(Factbase.new, q).to_term.to_s, q)
21
+ assert_equal(q, Factbase::Syntax.new(q).to_term.to_s, q)
22
22
  end
23
23
  end
24
24
 
@@ -28,7 +28,7 @@ class TestSyntax < Factbase::Test
28
28
  '(foo (bar (a (b c (f $bar)))))' => true,
29
29
  '(foo (bar (a (b c (f bar)))))' => false
30
30
  }.each do |q, a|
31
- assert_equal(a, Factbase::Syntax.new(Factbase.new, q).to_term.abstract?)
31
+ assert_equal(a, Factbase::Syntax.new(q).to_term.abstract?)
32
32
  end
33
33
  end
34
34
 
@@ -38,7 +38,7 @@ class TestSyntax < Factbase::Test
38
38
  '(foo "bar")' => true,
39
39
  '(agg (always) (max id))' => true
40
40
  }.each do |q, a|
41
- assert_equal(a, Factbase::Syntax.new(Factbase.new, q).to_term.static?)
41
+ assert_equal(a, Factbase::Syntax.new(q).to_term.static?)
42
42
  end
43
43
  end
44
44
 
@@ -53,7 +53,7 @@ class TestSyntax < Factbase::Test
53
53
  "(foo 'Hello,\n\nworld!\r\t\n')\n",
54
54
  "(or ( a 4) (b 5) (always) (and (always) (c 5) \t\t(r 7 w8s w8is 'Foo')))"
55
55
  ].each do |q|
56
- refute_nil(Factbase::Syntax.new(Factbase.new, q).to_term)
56
+ refute_nil(Factbase::Syntax.new(q).to_term)
57
57
  end
58
58
  end
59
59
 
@@ -73,7 +73,7 @@ class TestSyntax < Factbase::Test
73
73
  '(eq t 3.0e+21)',
74
74
  "(foo (x (f (t (y 42 'Hey you'))) (never) (r 3)) y z)"
75
75
  ].each do |q|
76
- assert_equal(q, Factbase::Syntax.new(Factbase.new, q).to_term.to_s, q)
76
+ assert_equal(q, Factbase::Syntax.new(q).to_term.to_s, q)
77
77
  end
78
78
  end
79
79
 
@@ -88,7 +88,7 @@ class TestSyntax < Factbase::Test
88
88
  '(or (eq bar 888) (eq z 1))' => true,
89
89
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
90
90
  }.each do |k, v|
91
- assert_equal(v, Factbase::Syntax.new(Factbase.new, k).to_term.evaluate(m, []), k)
91
+ assert_equal(v, Factbase::Syntax.new(k).to_term.evaluate(m, [], Factbase.new), k)
92
92
  end
93
93
  end
94
94
 
@@ -113,7 +113,7 @@ class TestSyntax < Factbase::Test
113
113
  '"'
114
114
  ].each do |q|
115
115
  msg = assert_raises(q) do
116
- Factbase::Syntax.new(Factbase.new, q).to_term
116
+ Factbase::Syntax.new(q).to_term
117
117
  end.message
118
118
  assert_includes(msg, q, msg)
119
119
  end
@@ -127,7 +127,7 @@ class TestSyntax < Factbase::Test
127
127
  '\'', 'привет'
128
128
  ].shuffle.join(' . ')
129
129
  assert_raises(Factbase::Syntax::Broken, q) do
130
- Factbase::Syntax.new(Factbase.new, q).to_term
130
+ Factbase::Syntax.new(q).to_term
131
131
  end
132
132
  end
133
133
  end
@@ -138,21 +138,21 @@ class TestSyntax < Factbase::Test
138
138
  '(and (foo) (foo))' => '(foo)',
139
139
  '(and (foo) (or (and (eq a 1))) (eq a 1) (foo))' => '(and (foo) (eq a 1))'
140
140
  }.each do |s, t|
141
- assert_equal(t, Factbase::Syntax.new(Factbase.new, s).to_term.to_s)
141
+ assert_equal(t, Factbase::Syntax.new(s).to_term.to_s)
142
142
  end
143
143
  end
144
144
 
145
145
  def test_fails_when_term_is_not_a_class
146
- assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(foo 1)', term: 'hello') }
146
+ assert_raises(StandardError) { Factbase::Syntax.new('(foo 1)', term: 'hello') }
147
147
  end
148
148
 
149
149
  def test_fails_when_term_is_wrong_class
150
- assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(bar 1)', term: String).to_term }
150
+ assert_raises(StandardError) { Factbase::Syntax.new('(bar 1)', term: String).to_term }
151
151
  end
152
152
 
153
153
  def test_fails_when_term_is_incorrectly_defined_class
154
154
  assert_includes(
155
- assert_raises(StandardError) { Factbase::Syntax.new(Factbase.new, '(bar 1)', term: FakeTerm).to_term }.message,
155
+ assert_raises(StandardError) { Factbase::Syntax.new('(bar 1)', term: FakeTerm).to_term }.message,
156
156
  'wrong number of arguments'
157
157
  )
158
158
  end
@@ -14,9 +14,9 @@ require_relative '../../lib/factbase/fact'
14
14
  # License:: MIT
15
15
  class TestTee < Factbase::Test
16
16
  def test_two_facts
17
- prim = Factbase::Fact.new(Factbase.new, Mutex.new, {})
17
+ prim = Factbase::Fact.new({})
18
18
  prim.foo = 42
19
- upper = Factbase::Fact.new(Factbase.new, Mutex.new, {})
19
+ upper = Factbase::Fact.new({})
20
20
  upper.bar = 13
21
21
  t = Factbase::Tee.new(prim, upper)
22
22
  assert_equal(42, t.foo)
@@ -24,9 +24,9 @@ class TestTee < Factbase::Test
24
24
  end
25
25
 
26
26
  def test_all_properties
27
- prim = Factbase::Fact.new(Factbase.new, Mutex.new, {})
27
+ prim = Factbase::Fact.new({})
28
28
  prim.foo = 42
29
- upper = Factbase::Fact.new(Factbase.new, Mutex.new, {})
29
+ upper = Factbase::Fact.new({})
30
30
  upper.bar = 13
31
31
  t = Factbase::Tee.new(prim, upper)
32
32
  assert_includes(t.all_properties, 'foo')
@@ -35,18 +35,18 @@ class TestTee < Factbase::Test
35
35
 
36
36
  def test_recursively
37
37
  map = {}
38
- prim = Factbase::Fact.new(Factbase.new, Mutex.new, map)
38
+ prim = Factbase::Fact.new(map)
39
39
  prim.foo = 42
40
- t = Factbase::Tee.new(nil, { 'bar' => 7 })
40
+ t = Factbase::Tee.new(Factbase::Fact.new({}), { 'bar' => 7 })
41
41
  assert_equal(7, t['$bar'])
42
42
  t = Factbase::Tee.new(prim, t)
43
43
  assert_equal(7, t['$bar'])
44
44
  end
45
45
 
46
46
  def test_prints_to_string
47
- prim = Factbase::Fact.new(Factbase.new, Mutex.new, {})
47
+ prim = Factbase::Fact.new({})
48
48
  prim.foo = 42
49
- upper = Factbase::Fact.new(Factbase.new, Mutex.new, {})
49
+ upper = Factbase::Fact.new({})
50
50
  upper.bar = 13
51
51
  t = Factbase::Tee.new(prim, upper)
52
52
  assert_equal('[ foo: [42] ]', t.to_s)
@@ -4,6 +4,7 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require_relative '../test__helper'
7
+ require_relative '../../lib/factbase'
7
8
  require_relative '../../lib/factbase/term'
8
9
 
9
10
  # Term test.
@@ -12,65 +13,73 @@ require_relative '../../lib/factbase/term'
12
13
  # License:: MIT
13
14
  class TestTerm < Factbase::Test
14
15
  def test_false_matching
15
- t = Factbase::Term.new(Factbase.new, :never, [])
16
- refute(t.evaluate(fact('foo' => [100]), []))
16
+ t = Factbase::Term.new(:never, [])
17
+ refute(t.evaluate(fact('foo' => [100]), [], Factbase.new))
17
18
  end
18
19
 
19
20
  def test_size_matching
20
- t = Factbase::Term.new(Factbase.new, :size, [:foo])
21
- assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90]), []))
22
- assert_equal(0, t.evaluate(fact('bar' => 100), []))
21
+ t = Factbase::Term.new(:size, [:foo])
22
+ assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90]), [], Factbase.new))
23
+ assert_equal(0, t.evaluate(fact('bar' => 100), [], Factbase.new))
23
24
  end
24
25
 
25
26
  def test_exists_matching
26
- t = Factbase::Term.new(Factbase.new, :exists, [:foo])
27
- assert(t.evaluate(fact('foo' => [42, 12, -90]), []))
28
- refute(t.evaluate(fact('bar' => 100), []))
27
+ t = Factbase::Term.new(:exists, [:foo])
28
+ assert(t.evaluate(fact('foo' => [42, 12, -90]), [], Factbase.new))
29
+ refute(t.evaluate(fact('bar' => 100), [], Factbase.new))
29
30
  end
30
31
 
31
32
  def test_absent_matching
32
- t = Factbase::Term.new(Factbase.new, :absent, [:foo])
33
- assert(t.evaluate(fact('z' => [42, 12, -90]), []))
34
- refute(t.evaluate(fact('foo' => 100), []))
33
+ t = Factbase::Term.new(:absent, [:foo])
34
+ assert(t.evaluate(fact('z' => [42, 12, -90]), [], Factbase.new))
35
+ refute(t.evaluate(fact('foo' => 100), [], Factbase.new))
35
36
  end
36
37
 
37
38
  def test_type_matching
38
- t = Factbase::Term.new(Factbase.new, :type, [:foo])
39
- assert_equal('Integer', t.evaluate(fact('foo' => 42), []))
40
- assert_equal('Integer', t.evaluate(fact('foo' => [42]), []))
41
- assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), []))
42
- assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!'), []))
43
- assert_equal('Float', t.evaluate(fact('foo' => 3.14), []))
44
- assert_equal('Time', t.evaluate(fact('foo' => Time.now), []))
45
- assert_equal('Integer', t.evaluate(fact('foo' => 1_000_000_000_000_000), []))
46
- assert_equal('nil', t.evaluate(fact, []))
39
+ t = Factbase::Term.new(:type, [:foo])
40
+ assert_equal('Integer', t.evaluate(fact('foo' => 42), [], Factbase.new))
41
+ assert_equal('Integer', t.evaluate(fact('foo' => [42]), [], Factbase.new))
42
+ assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3]), [], Factbase.new))
43
+ assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!'), [], Factbase.new))
44
+ assert_equal('Float', t.evaluate(fact('foo' => 3.14), [], Factbase.new))
45
+ assert_equal('Time', t.evaluate(fact('foo' => Time.now), [], Factbase.new))
46
+ assert_equal('Integer', t.evaluate(fact('foo' => 1_000_000_000_000_000), [], Factbase.new))
47
+ assert_equal('nil', t.evaluate(fact, [], Factbase.new))
47
48
  end
48
49
 
49
50
  def test_past
50
- t = Factbase::Term.new(Factbase.new, :prev, [:foo])
51
- assert_nil(t.evaluate(fact('foo' => 4), []))
52
- assert_equal([4], t.evaluate(fact('foo' => 5), []))
51
+ t = Factbase::Term.new(:prev, [:foo])
52
+ assert_nil(t.evaluate(fact('foo' => 4), [], Factbase.new))
53
+ assert_equal([4], t.evaluate(fact('foo' => 5), [], Factbase.new))
53
54
  end
54
55
 
55
56
  def test_at
56
- t = Factbase::Term.new(Factbase.new, :at, [1, :foo])
57
- assert_nil(t.evaluate(fact('foo' => 4), []))
58
- assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
57
+ t = Factbase::Term.new(:at, [1, :foo])
58
+ assert_nil(t.evaluate(fact('foo' => 4), [], Factbase.new))
59
+ assert_equal(5, t.evaluate(fact('foo' => [4, 5]), [], Factbase.new))
59
60
  end
60
61
 
61
62
  def test_report_missing_term
62
- t = Factbase::Term.new(Factbase.new, :something, [])
63
+ t = Factbase::Term.new(:something, [])
63
64
  msg = assert_raises(StandardError) do
64
- t.evaluate(fact, [])
65
+ t.evaluate(fact, [], Factbase.new)
65
66
  end.message
66
67
  assert_includes(msg, 'not defined at (something)', msg)
67
68
  end
68
69
 
69
70
  def test_report_other_error
70
- t = Factbase::Term.new(Factbase.new, :at, [])
71
+ t = Factbase::Term.new(:at, [])
71
72
  msg = assert_raises(StandardError) do
72
- t.evaluate(fact, [])
73
+ t.evaluate(fact, [], Factbase.new)
73
74
  end.message
74
75
  assert_includes(msg, 'at (at)', msg)
75
76
  end
77
+
78
+ def test_redresses_itself
79
+ t = Factbase::Term.new(:something, [])
80
+ require_relative '../../lib/factbase/indexed/indexed_term'
81
+ t.redress!(Factbase::IndexedTerm)
82
+ require_relative '../../lib/factbase/cached/cached_term'
83
+ t.redress!(Factbase::CachedTerm)
84
+ end
76
85
  end
data/test/test__helper.rb CHANGED
@@ -22,7 +22,7 @@ require_relative '../lib/factbase'
22
22
  # Default methods for all tests.
23
23
  class Factbase::Test < Minitest::Test
24
24
  def fact(map = {})
25
- require 'factbase/fact'
26
- Factbase::Fact.new(Factbase.new, Mutex.new, map)
25
+ require_relative '../lib/factbase/fact'
26
+ Factbase::Fact.new(map)
27
27
  end
28
28
  end
@@ -41,6 +41,12 @@ class TestFactbase < Factbase::Test
41
41
  end
42
42
  end
43
43
 
44
+ def test_converts_query_to_term
45
+ fb = Factbase.new
46
+ term = fb.to_term('(eq foo 42)')
47
+ assert_equal('(eq foo 42)', term.to_s)
48
+ end
49
+
44
50
  def test_simple_setting
45
51
  fb = Factbase.new
46
52
  fb.insert
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-04 00:00:00.000000000 Z
10
+ date: 2025-03-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: backtrace
@@ -162,24 +162,45 @@ files:
162
162
  - benchmark/bench_query.rb
163
163
  - benchmark/bench_taped.rb
164
164
  - factbase.gemspec
165
+ - fixtures/stories/agg.yml
166
+ - fixtures/stories/always.yml
167
+ - fixtures/stories/as.yml
168
+ - fixtures/stories/count.yml
169
+ - fixtures/stories/eq.yml
170
+ - fixtures/stories/gt.yml
171
+ - fixtures/stories/join.yml
172
+ - fixtures/stories/max.yml
173
+ - fixtures/stories/min.yml
174
+ - fixtures/stories/nth.yml
175
+ - fixtures/stories/or.yml
176
+ - fixtures/stories/sprintf.yml
177
+ - fixtures/stories/sum.yml
165
178
  - lib/factbase.rb
166
179
  - lib/factbase/accum.rb
180
+ - lib/factbase/cached/cached_fact.rb
181
+ - lib/factbase/cached/cached_factbase.rb
182
+ - lib/factbase/cached/cached_query.rb
183
+ - lib/factbase/cached/cached_term.rb
167
184
  - lib/factbase/churn.rb
168
185
  - lib/factbase/fact.rb
169
186
  - lib/factbase/flatten.rb
187
+ - lib/factbase/indexed/indexed_fact.rb
188
+ - lib/factbase/indexed/indexed_factbase.rb
189
+ - lib/factbase/indexed/indexed_query.rb
190
+ - lib/factbase/indexed/indexed_term.rb
170
191
  - lib/factbase/inv.rb
171
192
  - lib/factbase/light.rb
172
193
  - lib/factbase/logged.rb
173
194
  - lib/factbase/pre.rb
174
195
  - lib/factbase/query.rb
175
- - lib/factbase/query_once.rb
176
196
  - lib/factbase/rules.rb
197
+ - lib/factbase/sync/sync_factbase.rb
198
+ - lib/factbase/sync/sync_query.rb
177
199
  - lib/factbase/syntax.rb
178
200
  - lib/factbase/tallied.rb
179
201
  - lib/factbase/taped.rb
180
202
  - lib/factbase/tee.rb
181
203
  - lib/factbase/term.rb
182
- - lib/factbase/term_once.rb
183
204
  - lib/factbase/terms/aggregates.rb
184
205
  - lib/factbase/terms/aliases.rb
185
206
  - lib/factbase/terms/casting.rb
@@ -195,6 +216,10 @@ files:
195
216
  - lib/factbase/to_xml.rb
196
217
  - lib/factbase/to_yaml.rb
197
218
  - renovate.json
219
+ - test/factbase/cached/test_cached_factbase.rb
220
+ - test/factbase/cached/test_cached_query.rb
221
+ - test/factbase/indexed/test_indexed_query.rb
222
+ - test/factbase/sync/test_sync_query.rb
198
223
  - test/factbase/terms/test_aggregates.rb
199
224
  - test/factbase/terms/test_aliases.rb
200
225
  - test/factbase/terms/test_casting.rb
@@ -1,54 +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 '../factbase'
7
-
8
- # Query with a cache, a decorator of another query.
9
- #
10
- # It is NOT thread-safe!
11
- #
12
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
13
- # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
14
- # License:: MIT
15
- class Factbase::QueryOnce
16
- # Constructor.
17
- # @param [Factbase] fb Factbase
18
- # @param [Factbase::Query] query Original query
19
- # @param [Array<Hash>] maps Where to search
20
- def initialize(fb, query, maps)
21
- @fb = fb
22
- @query = query
23
- @maps = maps
24
- end
25
-
26
- # Iterate facts one by one.
27
- # @param [Hash] params Optional params accessible in the query via the "$" symbol
28
- # @yield [Fact] Facts one-by-one
29
- # @return [Integer] Total number of facts yielded
30
- def each(params = {}, &)
31
- unless block_given?
32
- return to_enum(__method__, params) if Factbase::Syntax.new(@fb, @query).to_term.abstract?
33
- key = [@query.to_s, @maps.object_id]
34
- before = @fb.cache[key]
35
- @fb.cache[key] = to_enum(__method__, params).to_a if before.nil?
36
- return @fb.cache[key]
37
- end
38
- @query.each(params, &)
39
- end
40
-
41
- # Read a single value.
42
- # @param [Hash] params Optional params accessible in the query via the "$" symbol
43
- # @return The value evaluated
44
- def one(params = {})
45
- @query.one(params)
46
- end
47
-
48
- # Delete all facts that match the query.
49
- # @return [Integer] Total number of facts deleted
50
- def delete!
51
- @fb.cache.clear
52
- @query.delete!
53
- end
54
- end