factbase 0.0.50 → 0.0.51

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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.50'
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
@@ -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