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.
@@ -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