factbase 0.0.50 → 0.0.51
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 +1 -1
- data/Gemfile.lock +13 -20
- data/README.md +52 -35
- data/factbase.gemspec +1 -0
- data/lib/factbase/{tuples.rb → accum.rb} +39 -40
- data/lib/factbase/fact.rb +4 -9
- data/lib/factbase/looged.rb +1 -2
- data/lib/factbase/query.rb +7 -2
- data/lib/factbase/syntax.rb +2 -2
- data/lib/factbase/tee.rb +57 -0
- data/lib/factbase/term.rb +32 -278
- data/lib/factbase/terms/aggregates.rb +102 -0
- data/lib/factbase/terms/aliases.rb +56 -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 +62 -0
- data/test/factbase/terms/test_aliases.rb +76 -0
- data/test/factbase/terms/test_math.rb +99 -0
- data/test/factbase/test_accum.rb +70 -0
- data/test/factbase/test_query.rb +23 -8
- data/test/factbase/test_syntax.rb +1 -1
- data/test/factbase/test_tee.rb +43 -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,91 @@
|
|
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_relative '../../factbase'
|
24
|
+
|
25
|
+
# Math terms.
|
26
|
+
#
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
module Factbase::Term::Math
|
31
|
+
def plus(fact, maps)
|
32
|
+
arithmetic(:+, fact, maps)
|
33
|
+
end
|
34
|
+
|
35
|
+
def minus(fact, maps)
|
36
|
+
arithmetic(:-, fact, maps)
|
37
|
+
end
|
38
|
+
|
39
|
+
def times(fact, maps)
|
40
|
+
arithmetic(:*, fact, maps)
|
41
|
+
end
|
42
|
+
|
43
|
+
def div(fact, maps)
|
44
|
+
arithmetic(:/, fact, maps)
|
45
|
+
end
|
46
|
+
|
47
|
+
def zero(fact, maps)
|
48
|
+
assert_args(1)
|
49
|
+
vv = the_values(0, fact, maps)
|
50
|
+
return false if vv.nil?
|
51
|
+
vv.any? { |v| (v.is_a?(Integer) || v.is_a?(Float)) && v.zero? }
|
52
|
+
end
|
53
|
+
|
54
|
+
def eq(fact, maps)
|
55
|
+
cmp(:==, fact, maps)
|
56
|
+
end
|
57
|
+
|
58
|
+
def lt(fact, maps)
|
59
|
+
cmp(:<, fact, maps)
|
60
|
+
end
|
61
|
+
|
62
|
+
def gt(fact, maps)
|
63
|
+
cmp(:>, fact, maps)
|
64
|
+
end
|
65
|
+
|
66
|
+
def cmp(op, fact, maps)
|
67
|
+
assert_args(2)
|
68
|
+
lefts = the_values(0, fact, maps)
|
69
|
+
return false if lefts.nil?
|
70
|
+
rights = the_values(1, fact, maps)
|
71
|
+
return false if rights.nil?
|
72
|
+
lefts.any? do |l|
|
73
|
+
l = l.floor if l.is_a?(Time) && op == :==
|
74
|
+
rights.any? do |r|
|
75
|
+
r = r.floor if r.is_a?(Time) && op == :==
|
76
|
+
l.send(op, r)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def arithmetic(op, fact, maps)
|
82
|
+
assert_args(2)
|
83
|
+
lefts = the_values(0, fact, maps)
|
84
|
+
return nil if lefts.nil?
|
85
|
+
raise 'Too many values at first position, one expected' unless lefts.size == 1
|
86
|
+
rights = the_values(1, fact, maps)
|
87
|
+
return nil if rights.nil?
|
88
|
+
raise 'Too many values at second position, one expected' unless rights.size == 1
|
89
|
+
lefts[0].send(op, rights[0])
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,73 @@
|
|
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_relative '../../factbase'
|
24
|
+
|
25
|
+
# Meta terms.
|
26
|
+
#
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
module Factbase::Term::Meta
|
31
|
+
def exists(fact, _maps)
|
32
|
+
assert_args(1)
|
33
|
+
!by_symbol(0, fact).nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def absent(fact, _maps)
|
37
|
+
assert_args(1)
|
38
|
+
by_symbol(0, fact).nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def size(fact, _maps)
|
42
|
+
assert_args(1)
|
43
|
+
v = by_symbol(0, fact)
|
44
|
+
return 0 if v.nil?
|
45
|
+
return 1 unless v.is_a?(Array)
|
46
|
+
v.size
|
47
|
+
end
|
48
|
+
|
49
|
+
def type(fact, _maps)
|
50
|
+
assert_args(1)
|
51
|
+
v = by_symbol(0, fact)
|
52
|
+
return 'nil' if v.nil?
|
53
|
+
v = v[0] if v.is_a?(Array) && v.size == 1
|
54
|
+
v.class.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def nil(fact, maps)
|
58
|
+
assert_args(1)
|
59
|
+
the_values(0, fact, maps).nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def many(fact, maps)
|
63
|
+
assert_args(1)
|
64
|
+
v = the_values(0, fact, maps)
|
65
|
+
!v.nil? && v.size > 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def one(fact, maps)
|
69
|
+
assert_args(1)
|
70
|
+
v = the_values(0, fact, maps)
|
71
|
+
!v.nil? && v.size == 1
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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_relative '../../factbase'
|
24
|
+
|
25
|
+
# Ordering terms.
|
26
|
+
#
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
module Factbase::Term::Ordering
|
31
|
+
def prev(fact, maps)
|
32
|
+
assert_args(1)
|
33
|
+
before = @prev
|
34
|
+
v = the_values(0, fact, maps)
|
35
|
+
@prev = v
|
36
|
+
before
|
37
|
+
end
|
38
|
+
|
39
|
+
def unique(fact, _maps)
|
40
|
+
@uniques = [] if @uniques.nil?
|
41
|
+
assert_args(1)
|
42
|
+
vv = by_symbol(0, fact)
|
43
|
+
return false if vv.nil?
|
44
|
+
vv = [vv] unless vv.is_a?(Array)
|
45
|
+
vv.each do |v|
|
46
|
+
return false if @uniques.include?(v)
|
47
|
+
@uniques << v
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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_relative '../../factbase'
|
24
|
+
|
25
|
+
# String terms.
|
26
|
+
#
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
module Factbase::Term::Strings
|
31
|
+
def matches(fact, maps)
|
32
|
+
assert_args(2)
|
33
|
+
str = the_values(0, fact, maps)
|
34
|
+
return false if str.nil?
|
35
|
+
raise 'Exactly one string expected' unless str.size == 1
|
36
|
+
re = the_values(1, fact, maps)
|
37
|
+
raise 'Regexp is nil' if re.nil?
|
38
|
+
raise 'Exactly one regexp expected' unless re.size == 1
|
39
|
+
str[0].to_s.match?(re[0])
|
40
|
+
end
|
41
|
+
end
|
data/lib/factbase/to_xml.rb
CHANGED
@@ -59,11 +59,11 @@ class Factbase::ToXML
|
|
59
59
|
if vv.is_a?(Array)
|
60
60
|
xml.send(:"#{k}_") do
|
61
61
|
vv.each do |v|
|
62
|
-
xml.send(:v, to_str(v))
|
62
|
+
xml.send(:v, to_str(v), t: type_of(v))
|
63
63
|
end
|
64
64
|
end
|
65
65
|
else
|
66
|
-
xml.send(:"#{k}_", to_str(vv))
|
66
|
+
xml.send(:"#{k}_", to_str(vv), t: type_of(vv))
|
67
67
|
end
|
68
68
|
end
|
69
69
|
end
|
@@ -81,4 +81,8 @@ class Factbase::ToXML
|
|
81
81
|
val.to_s
|
82
82
|
end
|
83
83
|
end
|
84
|
+
|
85
|
+
def type_of(val)
|
86
|
+
val.class.to_s[0]
|
87
|
+
end
|
84
88
|
end
|
data/lib/factbase.rb
CHANGED
@@ -74,12 +74,14 @@ require 'yaml'
|
|
74
74
|
# It's important to use +binwrite+ and +binread+, because the content is
|
75
75
|
# a chain of bytes, not a text.
|
76
76
|
#
|
77
|
+
# It is NOT thread-safe!
|
78
|
+
#
|
77
79
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
78
80
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
79
81
|
# License:: MIT
|
80
82
|
class Factbase
|
81
83
|
# Current version of the gem (changed by .rultor.yml on every release)
|
82
|
-
VERSION = '0.0.
|
84
|
+
VERSION = '0.0.51'
|
83
85
|
|
84
86
|
# An exception that may be thrown in a transaction, to roll it back.
|
85
87
|
class Rollback < StandardError; end
|
@@ -0,0 +1,62 @@
|
|
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/term'
|
25
|
+
require_relative '../../../lib/factbase/syntax'
|
26
|
+
|
27
|
+
# Aggregates test.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
class TestAggregates < Minitest::Test
|
32
|
+
def test_aggregation
|
33
|
+
maps = [
|
34
|
+
{ 'x' => 1, 'y' => 0, 'z' => 4 },
|
35
|
+
{ 'x' => 2, 'y' => 42, 'z' => 3 },
|
36
|
+
{ 'x' => 3, 'y' => 42, 'z' => 5 },
|
37
|
+
{ 'x' => 4, 'y' => 42, 'z' => 2 },
|
38
|
+
{ 'x' => 5, 'y' => 42, 'z' => 1 },
|
39
|
+
{ 'x' => 8, 'y' => 0, 'z' => 6 }
|
40
|
+
]
|
41
|
+
{
|
42
|
+
'(eq x (agg (eq y 42) (min x)))' => '(eq x 2)',
|
43
|
+
'(eq z (agg (eq y 0) (max z)))' => '(eq x 8)',
|
44
|
+
'(eq x (agg (and (eq y 42) (gt z 1)) (max x)))' => '(eq x 4)',
|
45
|
+
'(and (eq x (agg (eq y 42) (min x))) (eq z 3))' => '(eq x 2)',
|
46
|
+
'(eq x (agg (eq y 0) (nth 0 x)))' => '(eq x 1)',
|
47
|
+
'(eq x (agg (eq y 0) (first x)))' => '(eq x 1)',
|
48
|
+
'(agg (eq foo 42) (always))' => '(eq x 1)'
|
49
|
+
}.each do |q, r|
|
50
|
+
t = Factbase::Syntax.new(q).to_term
|
51
|
+
f = maps.find { |m| t.evaluate(fact(m), maps) }
|
52
|
+
assert(!f.nil?, "nothing found by: #{q}")
|
53
|
+
assert(Factbase::Syntax.new(r).to_term.evaluate(fact(f), []))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def fact(map = {})
|
60
|
+
Factbase::Fact.new(Mutex.new, map)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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/term'
|
25
|
+
require_relative '../../../lib/factbase/syntax'
|
26
|
+
require_relative '../../../lib/factbase/accum'
|
27
|
+
|
28
|
+
# Aliases test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestAliases < Minitest::Test
|
33
|
+
def test_aliases
|
34
|
+
maps = [
|
35
|
+
{ 'x' => [1], 'y' => [0] },
|
36
|
+
{ 'x' => [2], 'y' => [42] }
|
37
|
+
]
|
38
|
+
{
|
39
|
+
'(as foo (plus x 1))' => '(exists foo)',
|
40
|
+
'(as foo (plus x y))' => '(gt foo 0)',
|
41
|
+
'(as foo (plus bar 1))' => '(absent foo)'
|
42
|
+
}.each do |q, r|
|
43
|
+
t = Factbase::Syntax.new(q).to_term
|
44
|
+
maps.each do |m|
|
45
|
+
f = Factbase::Accum.new(fact(m), {}, false)
|
46
|
+
next unless t.evaluate(f, maps)
|
47
|
+
assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_join
|
53
|
+
maps = [
|
54
|
+
{ 'x' => [1], 'y' => [0], 'z' => [4] },
|
55
|
+
{ 'x' => [2], 'bar' => [44, 55, 66] }
|
56
|
+
]
|
57
|
+
{
|
58
|
+
'(join "foo_*" (gt x 1))' => '(exists foo_x)',
|
59
|
+
'(join "foo_*" (exists bar))' => '(and (eq foo_bar 44) (eq foo_bar 55))',
|
60
|
+
'(join "foo_*" (eq fff 1))' => '(absent foo_bar)'
|
61
|
+
}.each do |q, r|
|
62
|
+
t = Factbase::Syntax.new(q).to_term
|
63
|
+
maps.each do |m|
|
64
|
+
f = Factbase::Accum.new(fact(m), {}, false)
|
65
|
+
next unless t.evaluate(f, maps)
|
66
|
+
assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def fact(map = {})
|
74
|
+
Factbase::Fact.new(Mutex.new, map)
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,99 @@
|
|
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/term'
|
25
|
+
|
26
|
+
# Aggregates test.
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
class TestMath < Minitest::Test
|
31
|
+
def test_simple
|
32
|
+
t = Factbase::Term.new(:eq, [:foo, 42])
|
33
|
+
assert(t.evaluate(fact('foo' => [42]), []))
|
34
|
+
assert(!t.evaluate(fact('foo' => 'Hello!'), []))
|
35
|
+
assert(!t.evaluate(fact('bar' => ['Hello!']), []))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_zero
|
39
|
+
t = Factbase::Term.new(:zero, [:foo])
|
40
|
+
assert(t.evaluate(fact('foo' => [0]), []))
|
41
|
+
assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 0, 9, 'fdsf']), []))
|
42
|
+
assert(!t.evaluate(fact('foo' => [100]), []))
|
43
|
+
assert(!t.evaluate(fact('foo' => []), []))
|
44
|
+
assert(!t.evaluate(fact('bar' => []), []))
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_eq
|
48
|
+
t = Factbase::Term.new(:eq, [:foo, 42])
|
49
|
+
assert(t.evaluate(fact('foo' => 42), []))
|
50
|
+
assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf']), []))
|
51
|
+
assert(!t.evaluate(fact('foo' => [100]), []))
|
52
|
+
assert(!t.evaluate(fact('foo' => []), []))
|
53
|
+
assert(!t.evaluate(fact('bar' => []), []))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_eq_time
|
57
|
+
now = Time.now
|
58
|
+
t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
|
59
|
+
assert(t.evaluate(fact('foo' => now), []))
|
60
|
+
assert(t.evaluate(fact('foo' => [now, Time.now]), []))
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_lt
|
64
|
+
t = Factbase::Term.new(:lt, [:foo, 42])
|
65
|
+
assert(t.evaluate(fact('foo' => [10]), []))
|
66
|
+
assert(!t.evaluate(fact('foo' => [100]), []))
|
67
|
+
assert(!t.evaluate(fact('foo' => 100), []))
|
68
|
+
assert(!t.evaluate(fact('bar' => 100), []))
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_gt
|
72
|
+
t = Factbase::Term.new(:gt, [:foo, 42])
|
73
|
+
assert(t.evaluate(fact('foo' => [100]), []))
|
74
|
+
assert(t.evaluate(fact('foo' => 100), []))
|
75
|
+
assert(!t.evaluate(fact('foo' => [10]), []))
|
76
|
+
assert(!t.evaluate(fact('foo' => 10), []))
|
77
|
+
assert(!t.evaluate(fact('bar' => 10), []))
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_lt_time
|
81
|
+
t = Factbase::Term.new(:lt, [:foo, Time.now])
|
82
|
+
assert(t.evaluate(fact('foo' => [Time.now - 100]), []))
|
83
|
+
assert(!t.evaluate(fact('foo' => [Time.now + 100]), []))
|
84
|
+
assert(!t.evaluate(fact('bar' => [100]), []))
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_gt_time
|
88
|
+
t = Factbase::Term.new(:gt, [:foo, Time.now])
|
89
|
+
assert(t.evaluate(fact('foo' => [Time.now + 100]), []))
|
90
|
+
assert(!t.evaluate(fact('foo' => [Time.now - 100]), []))
|
91
|
+
assert(!t.evaluate(fact('bar' => [100]), []))
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def fact(map = {})
|
97
|
+
Factbase::Fact.new(Mutex.new, map)
|
98
|
+
end
|
99
|
+
end
|
@@ -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
|
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,7 +104,7 @@ 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)
|
@@ -112,7 +114,7 @@ class TestQuery < Minitest::Test
|
|
112
114
|
maps = []
|
113
115
|
maps << { 'foo' => [42] }
|
114
116
|
maps << { 'bar' => [4, 5] }
|
115
|
-
maps << { 'bar' => 5 }
|
117
|
+
maps << { 'bar' => [5] }
|
116
118
|
q = Factbase::Query.new(maps, Mutex.new, '(never)')
|
117
119
|
assert_equal(0, q.delete!)
|
118
120
|
assert_equal(3, maps.size)
|
@@ -126,8 +128,21 @@ class TestQuery < Minitest::Test
|
|
126
128
|
|
127
129
|
def test_returns_int
|
128
130
|
maps = []
|
129
|
-
maps << { 'foo' => 1 }
|
131
|
+
maps << { 'foo' => [1] }
|
130
132
|
q = Factbase::Query.new(maps, Mutex.new, '(eq foo 1)')
|
131
133
|
assert_equal(1, q.each(&:to_s))
|
132
134
|
end
|
135
|
+
|
136
|
+
def test_with_aliases
|
137
|
+
maps = []
|
138
|
+
maps << { 'foo' => [42] }
|
139
|
+
assert_equal(45, Factbase::Query.new(maps, Mutex.new, '(as bar (plus foo 3))').each.to_a[0].bar)
|
140
|
+
assert_equal(1, maps[0].size)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_with_nil_alias
|
144
|
+
maps = []
|
145
|
+
maps << { 'foo' => [42] }
|
146
|
+
assert(Factbase::Query.new(maps, Mutex.new, '(as bar (plus xxx 3))').each.to_a[0]['bar'].nil?)
|
147
|
+
end
|
133
148
|
end
|