factbase 0.0.50 → 0.0.52
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/Gemfile +4 -4
- data/Gemfile.lock +19 -26
- data/README.md +57 -35
- data/factbase.gemspec +1 -0
- data/lib/factbase/{tuples.rb → accum.rb} +40 -40
- data/lib/factbase/fact.rb +4 -9
- data/lib/factbase/looged.rb +18 -5
- data/lib/factbase/query.rb +27 -5
- data/lib/factbase/rules.rb +6 -2
- data/lib/factbase/syntax.rb +2 -2
- data/lib/factbase/tee.rb +59 -0
- data/lib/factbase/term.rb +32 -278
- data/lib/factbase/terms/aggregates.rb +109 -0
- data/lib/factbase/terms/aliases.rb +60 -0
- data/lib/factbase/terms/debug.rb +39 -0
- data/lib/factbase/terms/defn.rb +54 -0
- data/lib/factbase/terms/logical.rb +95 -0
- data/lib/factbase/terms/math.rb +91 -0
- data/lib/factbase/terms/meta.rb +73 -0
- data/lib/factbase/terms/ordering.rb +51 -0
- data/lib/factbase/terms/strings.rb +41 -0
- data/lib/factbase/to_xml.rb +6 -2
- data/lib/factbase.rb +3 -1
- data/test/factbase/terms/test_aggregates.rb +76 -0
- data/test/factbase/terms/test_aliases.rb +79 -0
- data/test/factbase/terms/test_math.rb +99 -0
- data/test/factbase/test_accum.rb +70 -0
- data/test/factbase/test_looged.rb +8 -0
- data/test/factbase/test_query.rb +51 -8
- data/test/factbase/test_rules.rb +8 -1
- data/test/factbase/test_syntax.rb +1 -1
- data/test/factbase/test_tee.rb +53 -0
- data/test/factbase/test_term.rb +7 -76
- data/test/factbase/test_to_xml.rb +26 -2
- data/test/test_factbase.rb +3 -3
- metadata +32 -4
- data/test/factbase/test_tuples.rb +0 -106
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require_relative '../../lib/factbase'
|
25
|
+
require_relative '../../lib/factbase/accum'
|
26
|
+
require_relative '../../lib/factbase/fact'
|
27
|
+
|
28
|
+
# Accum test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestAccum < Minitest::Test
|
33
|
+
def test_holds_props
|
34
|
+
map = {}
|
35
|
+
f = Factbase::Fact.new(Mutex.new, map)
|
36
|
+
props = {}
|
37
|
+
a = Factbase::Accum.new(f, props, false)
|
38
|
+
a.foo = 42
|
39
|
+
assert_raises { f.foo }
|
40
|
+
assert_equal(42, a.foo)
|
41
|
+
assert_equal([42], props['foo'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_passes_props
|
45
|
+
map = {}
|
46
|
+
f = Factbase::Fact.new(Mutex.new, map)
|
47
|
+
props = {}
|
48
|
+
a = Factbase::Accum.new(f, props, true)
|
49
|
+
a.foo = 42
|
50
|
+
assert_equal(42, f.foo)
|
51
|
+
assert_equal(42, a.foo)
|
52
|
+
assert_equal([42], props['foo'])
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_appends_props
|
56
|
+
map = {}
|
57
|
+
f = Factbase::Fact.new(Mutex.new, map)
|
58
|
+
f.foo = 42
|
59
|
+
props = {}
|
60
|
+
a = Factbase::Accum.new(f, props, false)
|
61
|
+
a.foo = 55
|
62
|
+
assert_equal(2, a['foo'].size)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_empties
|
66
|
+
f = Factbase::Fact.new(Mutex.new, {})
|
67
|
+
a = Factbase::Accum.new(f, {}, false)
|
68
|
+
assert(a['foo'].nil?)
|
69
|
+
end
|
70
|
+
end
|
@@ -45,6 +45,14 @@ class TestLooged < Minitest::Test
|
|
45
45
|
assert_equal(2, fb.size)
|
46
46
|
end
|
47
47
|
|
48
|
+
def test_reading_one
|
49
|
+
fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
|
50
|
+
fb.insert
|
51
|
+
fb.insert.bar = 42
|
52
|
+
assert_equal(1, fb.query('(agg (exists bar) (count))').one)
|
53
|
+
assert_equal([42], fb.query('(agg (exists bar) (first bar))').one)
|
54
|
+
end
|
55
|
+
|
48
56
|
def test_with_txn
|
49
57
|
log = Loog::Buffer.new
|
50
58
|
fb = Factbase::Looged.new(Factbase.new, log)
|
data/test/factbase/test_query.rb
CHANGED
@@ -44,9 +44,9 @@ class TestQuery < Minitest::Test
|
|
44
44
|
|
45
45
|
def test_complex_parsing
|
46
46
|
maps = []
|
47
|
-
maps << { 'num' => 42, 'name' => 'Jeff' }
|
48
|
-
maps << { 'pi' => 3.14, 'num' => [42, 66, 0], 'name' => 'peter' }
|
49
|
-
maps << { 'time' => Time.now - 100, 'num' => 0, 'hi' => [4], 'nome' => ['Walter'] }
|
47
|
+
maps << { 'num' => [42], 'name' => ['Jeff'] }
|
48
|
+
maps << { 'pi' => [3.14], 'num' => [42, 66, 0], 'name' => ['peter'] }
|
49
|
+
maps << { 'time' => [Time.now - 100], 'num' => [0], 'hi' => [4], 'nome' => ['Walter'] }
|
50
50
|
{
|
51
51
|
'(eq num 444)' => 0,
|
52
52
|
'(eq hi 4)' => 1,
|
@@ -65,6 +65,7 @@ class TestQuery < Minitest::Test
|
|
65
65
|
'(unique pi)' => 1,
|
66
66
|
'(many num)' => 1,
|
67
67
|
'(one num)' => 2,
|
68
|
+
'(nil (agg (exists hello) (min num)))' => 3,
|
68
69
|
'(gt num (minus 1 (either (at 0 (prev num)) 0)))' => 3,
|
69
70
|
'(and (not (many num)) (eq num (plus 21 +21)))' => 1,
|
70
71
|
'(and (not (many num)) (eq num (minus -100 -142)))' => 1,
|
@@ -84,7 +85,8 @@ class TestQuery < Minitest::Test
|
|
84
85
|
'(and (absent time) (exists pi))' => 1,
|
85
86
|
"(and (exists time) (not (\t\texists pi)))" => 1,
|
86
87
|
'(undef something)' => 3,
|
87
|
-
"(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
|
88
|
+
"(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1,
|
89
|
+
'(eq 3 (agg (eq num $num) (count)))' => 1
|
88
90
|
}.each do |q, r|
|
89
91
|
assert_equal(r, Factbase::Query.new(maps, Mutex.new, q).each.to_a.size, q)
|
90
92
|
end
|
@@ -93,7 +95,7 @@ class TestQuery < Minitest::Test
|
|
93
95
|
def test_simple_parsing_with_time
|
94
96
|
maps = []
|
95
97
|
now = Time.now.utc
|
96
|
-
maps << { 'foo' => now }
|
98
|
+
maps << { 'foo' => [now] }
|
97
99
|
q = Factbase::Query.new(maps, Mutex.new, "(eq foo #{now.iso8601})")
|
98
100
|
assert_equal(1, q.each.to_a.size)
|
99
101
|
end
|
@@ -102,17 +104,32 @@ class TestQuery < Minitest::Test
|
|
102
104
|
maps = []
|
103
105
|
maps << { 'foo' => [42] }
|
104
106
|
maps << { 'bar' => [4, 5] }
|
105
|
-
maps << { 'bar' => 5 }
|
107
|
+
maps << { 'bar' => [5] }
|
106
108
|
q = Factbase::Query.new(maps, Mutex.new, '(eq bar 5)')
|
107
109
|
assert_equal(2, q.delete!)
|
108
110
|
assert_equal(1, maps.size)
|
109
111
|
end
|
110
112
|
|
113
|
+
def test_reading_one
|
114
|
+
maps = []
|
115
|
+
maps << { 'foo' => [42] }
|
116
|
+
maps << { 'bar' => [4, 5] }
|
117
|
+
{
|
118
|
+
'(agg (exists foo) (first foo))' => [42],
|
119
|
+
'(agg (exists z) (first z))' => nil,
|
120
|
+
'(agg (always) (count))' => 2,
|
121
|
+
'(agg (eq bar $v) (count))' => 1,
|
122
|
+
'(agg (eq z 40) (count))' => 0
|
123
|
+
}.each do |q, r|
|
124
|
+
assert_equal(r, Factbase::Query.new(maps, Mutex.new, q).one(v: 4), "#{q} -> #{r}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
111
128
|
def test_deleting_nothing
|
112
129
|
maps = []
|
113
130
|
maps << { 'foo' => [42] }
|
114
131
|
maps << { 'bar' => [4, 5] }
|
115
|
-
maps << { 'bar' => 5 }
|
132
|
+
maps << { 'bar' => [5] }
|
116
133
|
q = Factbase::Query.new(maps, Mutex.new, '(never)')
|
117
134
|
assert_equal(0, q.delete!)
|
118
135
|
assert_equal(3, maps.size)
|
@@ -126,8 +143,34 @@ class TestQuery < Minitest::Test
|
|
126
143
|
|
127
144
|
def test_returns_int
|
128
145
|
maps = []
|
129
|
-
maps << { 'foo' => 1 }
|
146
|
+
maps << { 'foo' => [1] }
|
130
147
|
q = Factbase::Query.new(maps, Mutex.new, '(eq foo 1)')
|
131
148
|
assert_equal(1, q.each(&:to_s))
|
132
149
|
end
|
150
|
+
|
151
|
+
def test_with_aliases
|
152
|
+
maps = []
|
153
|
+
maps << { 'foo' => [42] }
|
154
|
+
assert_equal(45, Factbase::Query.new(maps, Mutex.new, '(as bar (plus foo 3))').each.to_a[0].bar)
|
155
|
+
assert_equal(1, maps[0].size)
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_with_params
|
159
|
+
maps = []
|
160
|
+
maps << { 'foo' => [42] }
|
161
|
+
maps << { 'foo' => [17] }
|
162
|
+
found = 0
|
163
|
+
Factbase::Query.new(maps, Mutex.new, '(eq foo $bar)').each(bar: [42]) do
|
164
|
+
found += 1
|
165
|
+
end
|
166
|
+
assert_equal(1, found)
|
167
|
+
assert_equal(1, Factbase::Query.new(maps, Mutex.new, '(eq foo $bar)').each(bar: 42).to_a.size)
|
168
|
+
assert_equal(0, Factbase::Query.new(maps, Mutex.new, '(eq foo $bar)').each(bar: 555).to_a.size)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_with_nil_alias
|
172
|
+
maps = []
|
173
|
+
maps << { 'foo' => [42] }
|
174
|
+
assert(Factbase::Query.new(maps, Mutex.new, '(as bar (plus xxx 3))').each.to_a[0]['bar'].nil?)
|
175
|
+
end
|
133
176
|
end
|
data/test/factbase/test_rules.rb
CHANGED
@@ -53,6 +53,13 @@ class TestRules < Minitest::Test
|
|
53
53
|
assert(f.to_s.length.positive?)
|
54
54
|
end
|
55
55
|
|
56
|
+
def test_query_one
|
57
|
+
fb = Factbase::Rules.new(Factbase.new, '(always)')
|
58
|
+
f = fb.insert
|
59
|
+
f.foo = 42
|
60
|
+
assert_equal(1, fb.query('(agg (eq foo $v) (count))').one(v: 42))
|
61
|
+
end
|
62
|
+
|
56
63
|
def test_check_only_when_txn_is_closed
|
57
64
|
fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
|
58
65
|
ok = false
|
@@ -94,6 +101,6 @@ class TestRules < Minitest::Test
|
|
94
101
|
end
|
95
102
|
end
|
96
103
|
assert(ok)
|
97
|
-
assert_equal(0, fb.query('(eq hello
|
104
|
+
assert_equal(0, fb.query('(eq hello $v)').each(v: 42).to_a.size)
|
98
105
|
end
|
99
106
|
end
|
@@ -43,7 +43,7 @@ class TestSyntax < Minitest::Test
|
|
43
43
|
[
|
44
44
|
'(foo)',
|
45
45
|
'(foo (bar) (zz 77) ) # hey',
|
46
|
-
"# hello\n\n\n(foo (bar))",
|
46
|
+
"# hello\n\n\n(foo ($bar))",
|
47
47
|
"(eq foo \n\n 'Hello, world!'\n)\n",
|
48
48
|
"(eq x 'Hello, \\' \n) \\' ( world!')",
|
49
49
|
"# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require_relative '../../lib/factbase'
|
25
|
+
require_relative '../../lib/factbase/tee'
|
26
|
+
require_relative '../../lib/factbase/fact'
|
27
|
+
|
28
|
+
# Tee test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestTee < Minitest::Test
|
33
|
+
def test_two_facts
|
34
|
+
map = {}
|
35
|
+
prim = Factbase::Fact.new(Mutex.new, map)
|
36
|
+
prim.foo = 42
|
37
|
+
upper = Factbase::Fact.new(Mutex.new, map)
|
38
|
+
upper.bar = 13
|
39
|
+
t = Factbase::Tee.new(prim, upper)
|
40
|
+
assert_equal(42, t.foo)
|
41
|
+
assert_equal([13], t['$bar'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_recursively
|
45
|
+
map = {}
|
46
|
+
prim = Factbase::Fact.new(Mutex.new, map)
|
47
|
+
prim.foo = 42
|
48
|
+
t = Factbase::Tee.new(nil, { 'bar' => 7 })
|
49
|
+
assert_equal(7, t['$bar'])
|
50
|
+
t = Factbase::Tee.new(prim, t)
|
51
|
+
assert_equal(7, t['$bar'])
|
52
|
+
end
|
53
|
+
end
|
data/test/factbase/test_term.rb
CHANGED
@@ -22,67 +22,12 @@
|
|
22
22
|
|
23
23
|
require 'minitest/autorun'
|
24
24
|
require_relative '../../lib/factbase/term'
|
25
|
-
require_relative '../../lib/factbase/syntax'
|
26
25
|
|
27
26
|
# Term test.
|
28
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
28
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
29
|
# License:: MIT
|
31
30
|
class TestTerm < Minitest::Test
|
32
|
-
def test_simple_matching
|
33
|
-
t = Factbase::Term.new(:eq, [:foo, 42])
|
34
|
-
assert(t.evaluate(fact('foo' => [42]), []))
|
35
|
-
assert(!t.evaluate(fact('foo' => 'Hello!'), []))
|
36
|
-
assert(!t.evaluate(fact('bar' => ['Hello!']), []))
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_eq_matching
|
40
|
-
t = Factbase::Term.new(:eq, [:foo, 42])
|
41
|
-
assert(t.evaluate(fact('foo' => 42), []))
|
42
|
-
assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf']), []))
|
43
|
-
assert(!t.evaluate(fact('foo' => [100]), []))
|
44
|
-
assert(!t.evaluate(fact('foo' => []), []))
|
45
|
-
assert(!t.evaluate(fact('bar' => []), []))
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_eq_matching_time
|
49
|
-
now = Time.now
|
50
|
-
t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
|
51
|
-
assert(t.evaluate(fact('foo' => now), []))
|
52
|
-
assert(t.evaluate(fact('foo' => [now, Time.now]), []))
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_lt_matching
|
56
|
-
t = Factbase::Term.new(:lt, [:foo, 42])
|
57
|
-
assert(t.evaluate(fact('foo' => [10]), []))
|
58
|
-
assert(!t.evaluate(fact('foo' => [100]), []))
|
59
|
-
assert(!t.evaluate(fact('foo' => 100), []))
|
60
|
-
assert(!t.evaluate(fact('bar' => 100), []))
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_gt_matching
|
64
|
-
t = Factbase::Term.new(:gt, [:foo, 42])
|
65
|
-
assert(t.evaluate(fact('foo' => [100]), []))
|
66
|
-
assert(t.evaluate(fact('foo' => 100), []))
|
67
|
-
assert(!t.evaluate(fact('foo' => [10]), []))
|
68
|
-
assert(!t.evaluate(fact('foo' => 10), []))
|
69
|
-
assert(!t.evaluate(fact('bar' => 10), []))
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_lt_matching_time
|
73
|
-
t = Factbase::Term.new(:lt, [:foo, Time.now])
|
74
|
-
assert(t.evaluate(fact('foo' => [Time.now - 100]), []))
|
75
|
-
assert(!t.evaluate(fact('foo' => [Time.now + 100]), []))
|
76
|
-
assert(!t.evaluate(fact('bar' => [100]), []))
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_gt_matching_time
|
80
|
-
t = Factbase::Term.new(:gt, [:foo, Time.now])
|
81
|
-
assert(t.evaluate(fact('foo' => [Time.now + 100]), []))
|
82
|
-
assert(!t.evaluate(fact('foo' => [Time.now - 100]), []))
|
83
|
-
assert(!t.evaluate(fact('bar' => [100]), []))
|
84
|
-
end
|
85
|
-
|
86
31
|
def test_false_matching
|
87
32
|
t = Factbase::Term.new(:never, [])
|
88
33
|
assert(!t.evaluate(fact('foo' => [100]), []))
|
@@ -125,6 +70,7 @@ class TestTerm < Minitest::Test
|
|
125
70
|
assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!'), []))
|
126
71
|
assert_equal('Float', t.evaluate(fact('foo' => 3.14), []))
|
127
72
|
assert_equal('Time', t.evaluate(fact('foo' => Time.now), []))
|
73
|
+
assert_equal('Integer', t.evaluate(fact('foo' => 1_000_000_000_000_000), []))
|
128
74
|
assert_equal('nil', t.evaluate(fact, []))
|
129
75
|
end
|
130
76
|
|
@@ -213,6 +159,12 @@ class TestTerm < Minitest::Test
|
|
213
159
|
assert_equal([42], t.evaluate(fact('foo' => 4), []))
|
214
160
|
end
|
215
161
|
|
162
|
+
def test_plus
|
163
|
+
t = Factbase::Term.new(:plus, [:foo, 42])
|
164
|
+
assert_equal(46, t.evaluate(fact('foo' => 4), []))
|
165
|
+
assert(t.evaluate(fact, []).nil?)
|
166
|
+
end
|
167
|
+
|
216
168
|
def test_report_missing_term
|
217
169
|
t = Factbase::Term.new(:something, [])
|
218
170
|
msg = assert_raises do
|
@@ -229,27 +181,6 @@ class TestTerm < Minitest::Test
|
|
229
181
|
assert(msg.include?('at (at)'), msg)
|
230
182
|
end
|
231
183
|
|
232
|
-
def test_aggregation
|
233
|
-
maps = [
|
234
|
-
{ 'x' => 1, 'y' => 0, 'z' => 4 },
|
235
|
-
{ 'x' => 2, 'y' => 42, 'z' => 3 },
|
236
|
-
{ 'x' => 3, 'y' => 42, 'z' => 5 },
|
237
|
-
{ 'x' => 4, 'y' => 42, 'z' => 2 },
|
238
|
-
{ 'x' => 5, 'y' => 42, 'z' => 1 },
|
239
|
-
{ 'x' => 8, 'y' => 0, 'z' => 6 }
|
240
|
-
]
|
241
|
-
{
|
242
|
-
'(eq x (agg (eq y 42) (min x)))' => '(eq x 2)',
|
243
|
-
'(eq z (agg (eq y 0) (max z)))' => '(eq x 8)',
|
244
|
-
'(eq x (agg (and (eq y 42) (gt z 1)) (max x)))' => '(eq x 4)',
|
245
|
-
'(and (eq x (agg (eq y 42) (min x))) (eq z 3))' => '(eq x 2)'
|
246
|
-
}.each do |q, r|
|
247
|
-
t = Factbase::Syntax.new(q).to_term
|
248
|
-
f = maps.find { |m| t.evaluate(fact(m), maps) }
|
249
|
-
assert(Factbase::Syntax.new(r).to_term.evaluate(fact(f), []))
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
184
|
private
|
254
185
|
|
255
186
|
def fact(map = {})
|
@@ -73,6 +73,27 @@ class TestToXML < Minitest::Test
|
|
73
73
|
assert(!xml.xpath('/fb/f/class').empty?)
|
74
74
|
end
|
75
75
|
|
76
|
+
def test_show_types_as_attributes
|
77
|
+
fb = Factbase.new
|
78
|
+
f = fb.insert
|
79
|
+
f.a = 42
|
80
|
+
f.b = 3.14
|
81
|
+
f.c = 'Hello'
|
82
|
+
f.d = Time.now
|
83
|
+
f.e = 'e'
|
84
|
+
f.e = 4
|
85
|
+
out = Factbase::ToXML.new(fb).xml
|
86
|
+
xml = Nokogiri::XML.parse(out)
|
87
|
+
[
|
88
|
+
'/fb/f/a[@t="I"]',
|
89
|
+
'/fb/f/b[@t="F"]',
|
90
|
+
'/fb/f/c[@t="S"]',
|
91
|
+
'/fb/f/d[@t="T"]',
|
92
|
+
'/fb/f/e/v[@t="S"]',
|
93
|
+
'/fb/f/e/v[@t="I"]'
|
94
|
+
].each { |x| assert(!xml.xpath(x).empty?, out) }
|
95
|
+
end
|
96
|
+
|
76
97
|
def test_sorts_keys
|
77
98
|
fb = Factbase.new
|
78
99
|
f = fb.insert
|
@@ -80,7 +101,10 @@ class TestToXML < Minitest::Test
|
|
80
101
|
f.t = 40
|
81
102
|
f.a = 10
|
82
103
|
f.c = 1
|
83
|
-
|
84
|
-
|
104
|
+
to = Factbase::ToXML.new(fb)
|
105
|
+
xml = Nokogiri::XML.parse(to.xml)
|
106
|
+
%w[a c t x].each_with_index do |e, i|
|
107
|
+
assert(!xml.xpath("/fb/f/#{e}[count(preceding-sibling::*) = #{i}]").empty?, e)
|
108
|
+
end
|
85
109
|
end
|
86
110
|
end
|
data/test/test_factbase.rb
CHANGED
@@ -68,15 +68,15 @@ class TestFactbase < Minitest::Test
|
|
68
68
|
fb.insert.bar = 1
|
69
69
|
fb.query('(exists bar)').each do |f|
|
70
70
|
f.bar = 42
|
71
|
-
assert_equal(
|
71
|
+
assert_equal(2, f['bar'].size)
|
72
72
|
end
|
73
73
|
found = 0
|
74
74
|
fb.query('(always)').each do |f|
|
75
|
-
assert_equal(
|
75
|
+
assert_equal(2, f['bar'].size)
|
76
76
|
found += 1
|
77
77
|
end
|
78
78
|
assert_equal(1, found)
|
79
|
-
assert_equal(
|
79
|
+
assert_equal(2, fb.query('(always)').each.to_a[0]['bar'].size)
|
80
80
|
end
|
81
81
|
|
82
82
|
def test_serialize_and_deserialize
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: factbase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.52
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-06-
|
11
|
+
date: 2024-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: backtrace
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: json
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,6 +129,7 @@ files:
|
|
115
129
|
- Rakefile
|
116
130
|
- factbase.gemspec
|
117
131
|
- lib/factbase.rb
|
132
|
+
- lib/factbase/accum.rb
|
118
133
|
- lib/factbase/fact.rb
|
119
134
|
- lib/factbase/flatten.rb
|
120
135
|
- lib/factbase/inv.rb
|
@@ -123,12 +138,25 @@ files:
|
|
123
138
|
- lib/factbase/query.rb
|
124
139
|
- lib/factbase/rules.rb
|
125
140
|
- lib/factbase/syntax.rb
|
141
|
+
- lib/factbase/tee.rb
|
126
142
|
- lib/factbase/term.rb
|
143
|
+
- lib/factbase/terms/aggregates.rb
|
144
|
+
- lib/factbase/terms/aliases.rb
|
145
|
+
- lib/factbase/terms/debug.rb
|
146
|
+
- lib/factbase/terms/defn.rb
|
147
|
+
- lib/factbase/terms/logical.rb
|
148
|
+
- lib/factbase/terms/math.rb
|
149
|
+
- lib/factbase/terms/meta.rb
|
150
|
+
- lib/factbase/terms/ordering.rb
|
151
|
+
- lib/factbase/terms/strings.rb
|
127
152
|
- lib/factbase/to_json.rb
|
128
153
|
- lib/factbase/to_xml.rb
|
129
154
|
- lib/factbase/to_yaml.rb
|
130
|
-
- lib/factbase/tuples.rb
|
131
155
|
- renovate.json
|
156
|
+
- test/factbase/terms/test_aggregates.rb
|
157
|
+
- test/factbase/terms/test_aliases.rb
|
158
|
+
- test/factbase/terms/test_math.rb
|
159
|
+
- test/factbase/test_accum.rb
|
132
160
|
- test/factbase/test_fact.rb
|
133
161
|
- test/factbase/test_flatten.rb
|
134
162
|
- test/factbase/test_inv.rb
|
@@ -137,11 +165,11 @@ files:
|
|
137
165
|
- test/factbase/test_query.rb
|
138
166
|
- test/factbase/test_rules.rb
|
139
167
|
- test/factbase/test_syntax.rb
|
168
|
+
- test/factbase/test_tee.rb
|
140
169
|
- test/factbase/test_term.rb
|
141
170
|
- test/factbase/test_to_json.rb
|
142
171
|
- test/factbase/test_to_xml.rb
|
143
172
|
- test/factbase/test_to_yaml.rb
|
144
|
-
- test/factbase/test_tuples.rb
|
145
173
|
- test/test__helper.rb
|
146
174
|
- test/test_factbase.rb
|
147
175
|
homepage: http://github.com/yegor256/factbase.rb
|
@@ -1,106 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright (c) 2024 Yegor Bugayenko
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
22
|
-
|
23
|
-
require 'minitest/autorun'
|
24
|
-
require_relative '../../lib/factbase'
|
25
|
-
require_relative '../../lib/factbase/tuples'
|
26
|
-
|
27
|
-
# Test.
|
28
|
-
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
-
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
-
# License:: MIT
|
31
|
-
class TestTuples < Minitest::Test
|
32
|
-
def test_passes_facts
|
33
|
-
fb = Factbase.new
|
34
|
-
f1 = fb.insert
|
35
|
-
f1.foo = 42
|
36
|
-
f2 = fb.insert
|
37
|
-
f2.bar = 55
|
38
|
-
Factbase::Tuples.new(fb, ['(exists foo)', '(exists bar)']).each do |a, b|
|
39
|
-
assert_equal(42, a.foo)
|
40
|
-
assert_equal(55, b.bar)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_with_empty_list_of_queries
|
45
|
-
fb = Factbase.new
|
46
|
-
f1 = fb.insert
|
47
|
-
f1.foo = 42
|
48
|
-
tuples = Factbase::Tuples.new(fb, [])
|
49
|
-
assert(tuples.each.to_a.empty?)
|
50
|
-
end
|
51
|
-
|
52
|
-
def test_is_reusable
|
53
|
-
fb = Factbase.new
|
54
|
-
f1 = fb.insert
|
55
|
-
f1.foo = 42
|
56
|
-
tuples = Factbase::Tuples.new(fb, ['(exists foo)'])
|
57
|
-
assert_equal(1, tuples.each.to_a.size)
|
58
|
-
assert_equal(1, tuples.each.to_a.size)
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_with_modifications
|
62
|
-
fb = Factbase.new
|
63
|
-
f1 = fb.insert
|
64
|
-
f1.foo = 42
|
65
|
-
Factbase::Tuples.new(fb, ['(exists foo)']).each do |fs|
|
66
|
-
fs[0].bar = 1
|
67
|
-
end
|
68
|
-
assert_equal(1, fb.query('(exists bar)').each.to_a.size)
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_with_txn
|
72
|
-
fb = Factbase.new
|
73
|
-
f1 = fb.insert
|
74
|
-
f1.foo = 42
|
75
|
-
Factbase::Tuples.new(fb, ['(exists foo)']).each do |fs|
|
76
|
-
fb.txn do |fbt|
|
77
|
-
f = fbt.insert
|
78
|
-
f.bar = 1
|
79
|
-
end
|
80
|
-
fs[0].xyz = 'hey'
|
81
|
-
end
|
82
|
-
assert_equal(1, fb.query('(exists bar)').each.to_a.size)
|
83
|
-
assert_equal(1, fb.query('(exists xyz)').each.to_a.size)
|
84
|
-
end
|
85
|
-
|
86
|
-
def test_with_chaining
|
87
|
-
fb = Factbase.new
|
88
|
-
f1 = fb.insert
|
89
|
-
f1.name = 'Jeff'
|
90
|
-
f1.friend = 'Walter'
|
91
|
-
f2 = fb.insert
|
92
|
-
f2.name = 'Walter'
|
93
|
-
f2.group = 1
|
94
|
-
f3 = fb.insert
|
95
|
-
f3.name = 'Donny'
|
96
|
-
f3.group = 1
|
97
|
-
tuples = Factbase::Tuples.new(
|
98
|
-
fb, ['(eq name "Jeff")', '(eq name "{f0.friend}")', '(eq group {f1.group})']
|
99
|
-
)
|
100
|
-
tuples.each do |fs|
|
101
|
-
assert_equal('Walter', fs[1].name)
|
102
|
-
assert(%w[Walter Donny].include?(fs[2].name))
|
103
|
-
end
|
104
|
-
assert_equal(2, tuples.each.to_a.size)
|
105
|
-
end
|
106
|
-
end
|