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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +13 -13
- data/README.md +24 -24
- data/REUSE.toml +7 -2
- data/Rakefile +8 -1
- data/benchmark/bench_factbase.rb +1 -1
- data/fixtures/stories/agg.yml +17 -0
- data/fixtures/stories/always.yml +16 -0
- data/fixtures/stories/as.yml +16 -0
- data/fixtures/stories/count.yml +18 -0
- data/fixtures/stories/eq.yml +30 -0
- data/fixtures/stories/gt.yml +18 -0
- data/fixtures/stories/join.yml +19 -0
- data/fixtures/stories/max.yml +14 -0
- data/fixtures/stories/min.yml +14 -0
- data/fixtures/stories/nth.yml +14 -0
- data/fixtures/stories/or.yml +18 -0
- data/fixtures/stories/sprintf.yml +12 -0
- data/fixtures/stories/sum.yml +14 -0
- data/lib/factbase/cached/cached_fact.rb +28 -0
- data/lib/factbase/cached/cached_factbase.rb +64 -0
- data/lib/factbase/cached/cached_query.rb +61 -0
- data/lib/factbase/cached/cached_term.rb +25 -0
- data/lib/factbase/fact.rb +13 -13
- data/lib/factbase/indexed/indexed_fact.rb +28 -0
- data/lib/factbase/indexed/indexed_factbase.rb +64 -0
- data/lib/factbase/indexed/indexed_query.rb +56 -0
- data/lib/factbase/indexed/indexed_term.rb +60 -0
- data/lib/factbase/inv.rb +18 -6
- data/lib/factbase/light.rb +7 -6
- data/lib/factbase/logged.rb +87 -62
- data/lib/factbase/query.rb +29 -34
- data/lib/factbase/rules.rb +16 -15
- data/lib/factbase/sync/sync_factbase.rb +57 -0
- data/lib/factbase/sync/sync_query.rb +61 -0
- data/lib/factbase/syntax.rb +12 -25
- data/lib/factbase/tallied.rb +11 -10
- data/lib/factbase/taped.rb +8 -0
- data/lib/factbase/tee.rb +2 -0
- data/lib/factbase/term.rb +45 -17
- data/lib/factbase/terms/aggregates.rb +17 -15
- data/lib/factbase/terms/aliases.rb +4 -4
- data/lib/factbase/terms/casting.rb +8 -8
- data/lib/factbase/terms/debug.rb +2 -2
- data/lib/factbase/terms/defn.rb +3 -3
- data/lib/factbase/terms/logical.rb +53 -14
- data/lib/factbase/terms/math.rb +26 -26
- data/lib/factbase/terms/meta.rb +14 -14
- data/lib/factbase/terms/ordering.rb +4 -4
- data/lib/factbase/terms/strings.rb +8 -8
- data/lib/factbase/terms/system.rb +3 -3
- data/lib/factbase.rb +67 -55
- data/test/factbase/cached/test_cached_factbase.rb +22 -0
- data/test/factbase/cached/test_cached_query.rb +79 -0
- data/test/factbase/indexed/test_indexed_query.rb +175 -0
- data/test/factbase/sync/test_sync_query.rb +30 -0
- data/test/factbase/terms/test_aggregates.rb +5 -5
- data/test/factbase/terms/test_aliases.rb +7 -7
- data/test/factbase/terms/test_casting.rb +8 -8
- data/test/factbase/terms/test_debug.rb +6 -6
- data/test/factbase/terms/test_defn.rb +14 -14
- data/test/factbase/terms/test_logical.rb +17 -19
- data/test/factbase/terms/test_math.rb +63 -61
- data/test/factbase/terms/test_meta.rb +36 -36
- data/test/factbase/terms/test_ordering.rb +9 -9
- data/test/factbase/terms/test_strings.rb +10 -10
- data/test/factbase/terms/test_system.rb +6 -6
- data/test/factbase/test_accum.rb +5 -5
- data/test/factbase/test_fact.rb +12 -12
- data/test/factbase/test_logged.rb +7 -0
- data/test/factbase/test_query.rb +110 -37
- data/test/factbase/test_rules.rb +1 -1
- data/test/factbase/test_syntax.rb +12 -12
- data/test/factbase/test_tee.rb +8 -8
- data/test/factbase/test_term.rb +39 -30
- data/test/test__helper.rb +2 -2
- data/test/test_factbase.rb +6 -0
- metadata +29 -4
- data/lib/factbase/query_once.rb +0 -54
- data/lib/factbase/term_once.rb +0 -67
data/test/factbase/test_query.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
}
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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(
|
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(
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
165
|
-
assert_equal(0, Factbase::Query.new(
|
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(
|
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(
|
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
|
data/test/factbase/test_rules.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
data/test/factbase/test_tee.rb
CHANGED
@@ -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(
|
17
|
+
prim = Factbase::Fact.new({})
|
18
18
|
prim.foo = 42
|
19
|
-
upper = Factbase::Fact.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(
|
27
|
+
prim = Factbase::Fact.new({})
|
28
28
|
prim.foo = 42
|
29
|
-
upper = Factbase::Fact.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(
|
38
|
+
prim = Factbase::Fact.new(map)
|
39
39
|
prim.foo = 42
|
40
|
-
t = Factbase::Tee.new(
|
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(
|
47
|
+
prim = Factbase::Fact.new({})
|
48
48
|
prim.foo = 42
|
49
|
-
upper = Factbase::Fact.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)
|
data/test/factbase/test_term.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
26
|
-
Factbase::Fact.new(
|
25
|
+
require_relative '../lib/factbase/fact'
|
26
|
+
Factbase::Fact.new(map)
|
27
27
|
end
|
28
28
|
end
|
data/test/test_factbase.rb
CHANGED
@@ -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.
|
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-
|
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
|
data/lib/factbase/query_once.rb
DELETED
@@ -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
|