factbase 0.0.51 → 0.0.53

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5185906fd48ccc7df8bfd07a8dd88ae4e7ce4636ec40b3cc3fc174cdeb347d1
4
- data.tar.gz: a0bc08ef0001d876b9cd26c31fd4a59f094f9ad88dbbb002e345ca4c47cc1a7f
3
+ metadata.gz: c1a97c8ab8b636aead2a98c980db09739120d98be8be0bc57f666a3c74ab5ef2
4
+ data.tar.gz: 595422041561719af19046aceef46e7a15c5ec2e6702edb0e1b8e4e22ddece0f
5
5
  SHA512:
6
- metadata.gz: ad553e4da47bb2a6af81a07a64d627dc03ebfe68f96000bc1862df8a25e5bd3cc1563d93ef3a184932af6af286e47912789ca232154cadef512cfc97a3341387
7
- data.tar.gz: b85a0a97e043e3bc4fa03396a3073c63f18cc268bf296174df66045b7ce47ccd082eb2cbe92d1cd1b07c7a43225ebcd830f624abccdd7d71bfae78825c05dcc5
6
+ metadata.gz: 30a0906b9c5846728ae7430502c4267d167637220b2c96e7c36ea3a0455044438d478fb2f59db84fa17d9ccae0bad73dff1025d3b70d8f54316faf59dc11e323
7
+ data.tar.gz: 9807c7818a2ead9bfc3dda9fc83435ea13c37da0cc802acf1798de5e0473660e3d747f50a4da0cd584fe5758e9827b64a20fb3991efaeefad21e83bf92c0dece
data/Gemfile CHANGED
@@ -23,11 +23,11 @@
23
23
  source 'https://rubygems.org'
24
24
  gemspec
25
25
 
26
- gem 'minitest', '5.23.1', require: false
26
+ gem 'minitest', '5.24.0', require: false
27
27
  gem 'rake', '13.2.1', require: false
28
- gem 'rspec-rails', '6.1.2', require: false
28
+ gem 'rspec-rails', '6.1.3', require: false
29
29
  gem 'rubocop', '1.64.1', require: false
30
- gem 'rubocop-performance', '1.21.0', require: false
30
+ gem 'rubocop-performance', '1.21.1', require: false
31
31
  gem 'rubocop-rspec', '3.0.1', require: false
32
32
  gem 'simplecov', '0.22.0', require: false
33
33
  gem 'simplecov-cobertura', '2.1.0', require: false
data/Gemfile.lock CHANGED
@@ -3,9 +3,11 @@ PATH
3
3
  specs:
4
4
  factbase (0.0.0)
5
5
  backtrace (~> 0.3)
6
+ decoor (~> 0.0)
6
7
  json (~> 2.7)
7
8
  loog (~> 0.2)
8
9
  nokogiri (~> 1.10)
10
+ others (~> 0.0)
9
11
  tago (~> 0.0)
10
12
  yaml (~> 0.3)
11
13
 
@@ -46,6 +48,7 @@ GEM
46
48
  concurrent-ruby (1.3.3)
47
49
  connection_pool (2.4.1)
48
50
  crass (1.0.6)
51
+ decoor (0.0.1)
49
52
  diff-lcs (1.5.1)
50
53
  docile (1.4.0)
51
54
  drb (2.2.1)
@@ -62,7 +65,7 @@ GEM
62
65
  crass (~> 1.0.2)
63
66
  nokogiri (>= 1.12.0)
64
67
  loog (0.5.1)
65
- minitest (5.23.1)
68
+ minitest (5.24.0)
66
69
  mutex_m (0.2.0)
67
70
  nokogiri (1.16.6-arm64-darwin)
68
71
  racc (~> 1.4)
@@ -72,6 +75,7 @@ GEM
72
75
  racc (~> 1.4)
73
76
  nokogiri (1.16.6-x86_64-linux)
74
77
  racc (~> 1.4)
78
+ others (0.0.3)
75
79
  parallel (1.25.1)
76
80
  parser (3.3.3.0)
77
81
  ast (~> 2.4.1)
@@ -79,7 +83,7 @@ GEM
79
83
  psych (5.1.2)
80
84
  stringio
81
85
  racc (1.8.0)
82
- rack (3.1.3)
86
+ rack (3.1.4)
83
87
  rack-session (2.0.0)
84
88
  rack (>= 3.0.0)
85
89
  rack-test (2.1.0)
@@ -119,7 +123,7 @@ GEM
119
123
  rspec-mocks (3.13.1)
120
124
  diff-lcs (>= 1.2.0, < 2.0)
121
125
  rspec-support (~> 3.13.0)
122
- rspec-rails (6.1.2)
126
+ rspec-rails (6.1.3)
123
127
  actionpack (>= 6.1)
124
128
  activesupport (>= 6.1)
125
129
  railties (>= 6.1)
@@ -141,7 +145,7 @@ GEM
141
145
  unicode-display_width (>= 2.4.0, < 3.0)
142
146
  rubocop-ast (1.31.3)
143
147
  parser (>= 3.3.1.0)
144
- rubocop-performance (1.21.0)
148
+ rubocop-performance (1.21.1)
145
149
  rubocop (>= 1.48.1, < 2.0)
146
150
  rubocop-ast (>= 1.31.1, < 2.0)
147
151
  rubocop-rspec (3.0.1)
@@ -176,11 +180,11 @@ PLATFORMS
176
180
 
177
181
  DEPENDENCIES
178
182
  factbase!
179
- minitest (= 5.23.1)
183
+ minitest (= 5.24.0)
180
184
  rake (= 13.2.1)
181
- rspec-rails (= 6.1.2)
185
+ rspec-rails (= 6.1.3)
182
186
  rubocop (= 1.64.1)
183
- rubocop-performance (= 1.21.0)
187
+ rubocop-performance (= 1.21.1)
184
188
  rubocop-rspec (= 3.0.1)
185
189
  simplecov (= 0.22.0)
186
190
  simplecov-cobertura (= 2.1.0)
data/README.md CHANGED
@@ -86,8 +86,9 @@ It's possible to modify the facts retrieved, on fly:
86
86
 
87
87
  * `(as p v)` adds property `p` with the value `v`
88
88
  * `(join s t)` adds properties named by the `s` mask with the values retrieved
89
- by the `t` term, for example, `(join "foo_*" (gt x 5))` will add `foo_x` and
90
- all other properties found in the facts that match `(gt x 5)`
89
+ by the `t` term, for example, `(join "x<=foo,y<=bar" (gt x 5))` will add
90
+ `x` and `y` properties, setting them to values found in the `foo` and `bar`
91
+ properties in the facts that match `(gt x 5)`
91
92
 
92
93
  Also, some simple arithmetic:
93
94
 
@@ -128,6 +129,10 @@ a positive integer)
128
129
  * `(min p)` returns the minimum
129
130
  * `(sum p)` returns the arithmetic sum of all values of the `p` property
130
131
 
132
+ It's also possible to use a sub-query in a shorter form than with the `agg`:
133
+
134
+ * `(empty q)` is true if the subquery `q` is empty
135
+
131
136
  ## How to contribute
132
137
 
133
138
  Read
data/factbase.gemspec CHANGED
@@ -43,9 +43,11 @@ Gem::Specification.new do |s|
43
43
  s.rdoc_options = ['--charset=UTF-8']
44
44
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
45
45
  s.add_runtime_dependency 'backtrace', '~>0.3'
46
+ s.add_runtime_dependency 'decoor', '~>0.0'
46
47
  s.add_runtime_dependency 'json', '~>2.7'
47
48
  s.add_runtime_dependency 'loog', '~>0.2'
48
49
  s.add_runtime_dependency 'nokogiri', '~>1.10'
50
+ s.add_runtime_dependency 'others', '~>0.0'
49
51
  s.add_runtime_dependency 'tago', '~>0.0'
50
52
  s.add_runtime_dependency 'yaml', '~>0.3'
51
53
  s.metadata['rubygems_mfa_required'] = 'true'
@@ -20,6 +20,7 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'others'
23
24
  require_relative '../factbase'
24
25
 
25
26
  # Accumulator of props.
@@ -39,37 +40,28 @@ class Factbase::Accum
39
40
  end
40
41
 
41
42
  def to_s
42
- @fact.to_s
43
+ "#{@fact} + #{@props}"
43
44
  end
44
45
 
45
- def method_missing(*args)
46
+ others do |*args|
46
47
  k = args[0].to_s
47
48
  if k.end_with?('=')
48
49
  kk = k[0..-2]
49
50
  @props[kk] = [] if @props[kk].nil?
50
51
  @props[kk] << args[1]
51
52
  @fact.method_missing(*args) if @pass
52
- return
53
- end
54
- if k == '[]'
53
+ elsif k == '[]'
55
54
  kk = args[1].to_s
56
55
  vv = @props[kk].nil? ? [] : @props[kk]
57
56
  vvv = @fact.method_missing(*args)
57
+ vvv = [vvv] unless vvv.nil? || vvv.is_a?(Array)
58
58
  vv += vvv unless vvv.nil?
59
59
  vv.uniq!
60
- return vv.empty? ? nil : vv
60
+ vv.empty? ? nil : vv
61
+ elsif @props[k].nil?
62
+ @fact.method_missing(*args)
63
+ else
64
+ @props[k][0]
61
65
  end
62
- return @props[k][0] unless @props[k].nil?
63
- @fact.method_missing(*args)
64
- end
65
-
66
- # rubocop:disable Style/OptionalBooleanParameter
67
- def respond_to?(_method, _include_private = false)
68
- # rubocop:enable Style/OptionalBooleanParameter
69
- true
70
- end
71
-
72
- def respond_to_missing?(_method, _include_private = false)
73
- true
74
66
  end
75
67
  end
data/lib/factbase/fact.rb CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'json'
24
24
  require 'time'
25
+ require 'others'
25
26
  require_relative '../factbase'
26
27
 
27
28
  # A single fact in a factbase.
@@ -57,7 +58,7 @@ class Factbase::Fact
57
58
  end
58
59
 
59
60
  # When a method is missing, this method is called.
60
- def method_missing(*args)
61
+ others do |*args|
61
62
  k = args[0].to_s
62
63
  if k.end_with?('=')
63
64
  kk = k[0..-2]
@@ -85,14 +86,4 @@ class Factbase::Fact
85
86
  v[0]
86
87
  end
87
88
  end
88
-
89
- # rubocop:disable Style/OptionalBooleanParameter
90
- def respond_to?(_method, _include_private = false)
91
- # rubocop:enable Style/OptionalBooleanParameter
92
- true
93
- end
94
-
95
- def respond_to_missing?(_method, _include_private = false)
96
- true
97
- end
98
89
  end
data/lib/factbase/inv.rb CHANGED
@@ -20,6 +20,8 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'others'
24
+ require 'decoor'
23
25
  require_relative '../factbase'
24
26
 
25
27
  # A decorator of a Factbase, that checks invariants on every set.
@@ -27,6 +29,8 @@ require_relative '../factbase'
27
29
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
30
  # License:: MIT
29
31
  class Factbase::Inv
32
+ decoor(:fb)
33
+
30
34
  def initialize(fb, &block)
31
35
  @fb = fb
32
36
  @block = block
@@ -36,10 +40,6 @@ class Factbase::Inv
36
40
  Factbase::Inv.new(@fb.dup, &@block)
37
41
  end
38
42
 
39
- def size
40
- @fb.size
41
- end
42
-
43
43
  def insert
44
44
  Fact.new(@fb.insert, @block)
45
45
  end
@@ -52,14 +52,6 @@ class Factbase::Inv
52
52
  @fb.txn(this, &)
53
53
  end
54
54
 
55
- def export
56
- @fb.export
57
- end
58
-
59
- def import(bytes)
60
- @fb.import(bytes)
61
- end
62
-
63
55
  # Fact decorator.
64
56
  #
65
57
  # This is an internal class, it is not supposed to be instantiated directly.
@@ -74,21 +66,11 @@ class Factbase::Inv
74
66
  @fact.to_s
75
67
  end
76
68
 
77
- def method_missing(*args)
69
+ others do |*args|
78
70
  k = args[0].to_s
79
71
  @block.call(k[0..-2], args[1]) if k.end_with?('=')
80
72
  @fact.method_missing(*args)
81
73
  end
82
-
83
- # rubocop:disable Style/OptionalBooleanParameter
84
- def respond_to?(_method, _include_private = false)
85
- # rubocop:enable Style/OptionalBooleanParameter
86
- true
87
- end
88
-
89
- def respond_to_missing?(_method, _include_private = false)
90
- true
91
- end
92
74
  end
93
75
 
94
76
  # Query decorator.
@@ -96,6 +78,8 @@ class Factbase::Inv
96
78
  # This is an internal class, it is not supposed to be instantiated directly.
97
79
  #
98
80
  class Query
81
+ decoor(:query)
82
+
99
83
  def initialize(query, block)
100
84
  @query = query
101
85
  @block = block
@@ -107,9 +91,5 @@ class Factbase::Inv
107
91
  yield Fact.new(f, @block)
108
92
  end
109
93
  end
110
-
111
- def delete!
112
- @query.delete!
113
- end
114
94
  end
115
95
  end
@@ -20,6 +20,7 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'others'
23
24
  require 'time'
24
25
  require 'loog'
25
26
  require 'tago'
@@ -31,18 +32,18 @@ require_relative 'syntax'
31
32
  # License:: MIT
32
33
  class Factbase::Looged
33
34
  def initialize(fb, loog)
35
+ raise 'The "fb" is nil' if fb.nil?
34
36
  @fb = fb
37
+ raise 'The "loog" is nil' if loog.nil?
35
38
  @loog = loog
36
39
  end
37
40
 
41
+ decoor(:fb)
42
+
38
43
  def dup
39
44
  Factbase::Looged.new(@fb.dup, @loog)
40
45
  end
41
46
 
42
- def size
43
- @fb.size
44
- end
45
-
46
47
  def insert
47
48
  f = @fb.insert
48
49
  @loog.debug("Inserted new fact ##{@fb.size}")
@@ -72,14 +73,6 @@ class Factbase::Looged
72
73
  r
73
74
  end
74
75
 
75
- def export
76
- @fb.export
77
- end
78
-
79
- def import(bytes)
80
- @fb.import(bytes)
81
- end
82
-
83
76
  # Fact decorator.
84
77
  #
85
78
  # This is an internal class, it is not supposed to be instantiated directly.
@@ -92,11 +85,7 @@ class Factbase::Looged
92
85
  @loog = loog
93
86
  end
94
87
 
95
- def to_s
96
- @fact.to_s
97
- end
98
-
99
- def method_missing(*args)
88
+ others do |*args|
100
89
  r = @fact.method_missing(*args)
101
90
  k = args[0].to_s
102
91
  v = args[1]
@@ -106,16 +95,6 @@ class Factbase::Looged
106
95
  @loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
107
96
  r
108
97
  end
109
-
110
- # rubocop:disable Style/OptionalBooleanParameter
111
- def respond_to?(_method, _include_private = false)
112
- # rubocop:enable Style/OptionalBooleanParameter
113
- true
114
- end
115
-
116
- def respond_to_missing?(_method, _include_private = false)
117
- true
118
- end
119
98
  end
120
99
 
121
100
  # Query decorator.
@@ -129,12 +108,26 @@ class Factbase::Looged
129
108
  @loog = loog
130
109
  end
131
110
 
132
- def each(&)
111
+ def one(params = {})
112
+ q = Factbase::Syntax.new(@expr).to_term.to_s
113
+ r = nil
114
+ tail = Factbase::Looged.elapsed do
115
+ r = @fb.query(@expr).one(params)
116
+ end
117
+ if r.nil?
118
+ @loog.debug("Nothing found by '#{q}' #{tail}")
119
+ else
120
+ @loog.debug("Found #{r} (#{r.class}) by '#{q}' #{tail}")
121
+ end
122
+ r
123
+ end
124
+
125
+ def each(params = {}, &)
133
126
  q = Factbase::Syntax.new(@expr).to_term.to_s
134
127
  if block_given?
135
128
  r = nil
136
129
  tail = Factbase::Looged.elapsed do
137
- r = @fb.query(@expr).each(&)
130
+ r = @fb.query(@expr).each(params, &)
138
131
  end
139
132
  raise ".each of #{@query.class} returned #{r.class}" unless r.is_a?(Integer)
140
133
  if r.zero?
@@ -146,7 +139,7 @@ class Factbase::Looged
146
139
  else
147
140
  array = []
148
141
  tail = Factbase::Looged.elapsed do
149
- @fb.query(@expr).each do |f|
142
+ @fb.query(@expr).each(params) do |f|
150
143
  array << f
151
144
  end
152
145
  end
data/lib/factbase/pre.rb CHANGED
@@ -21,6 +21,7 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'loog'
24
+ require 'decoor'
24
25
  require_relative '../factbase'
25
26
 
26
27
  # A decorator of a Factbase, that runs a provided block on every +insert+.
@@ -28,7 +29,10 @@ require_relative '../factbase'
28
29
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
30
  # License:: MIT
30
31
  class Factbase::Pre
32
+ decoor(:fb)
33
+
31
34
  def initialize(fb, &block)
35
+ raise 'The "fb" is nil' if fb.nil?
32
36
  @fb = fb
33
37
  @block = block
34
38
  end
@@ -37,29 +41,13 @@ class Factbase::Pre
37
41
  Factbase::Pre.new(@fb.dup, &@block)
38
42
  end
39
43
 
40
- def size
41
- @fb.size
42
- end
43
-
44
44
  def insert
45
45
  f = @fb.insert
46
46
  @block.call(f)
47
47
  f
48
48
  end
49
49
 
50
- def query(query)
51
- @fb.query(query)
52
- end
53
-
54
50
  def txn(this = self, &)
55
51
  @fb.txn(this, &)
56
52
  end
57
-
58
- def export
59
- @fb.export
60
- end
61
-
62
- def import(bytes)
63
- @fb.import(bytes)
64
- end
65
53
  end
@@ -24,6 +24,7 @@ require_relative '../factbase'
24
24
  require_relative 'syntax'
25
25
  require_relative 'fact'
26
26
  require_relative 'accum'
27
+ require_relative 'tee'
27
28
 
28
29
  # Query.
29
30
  #
@@ -46,16 +47,19 @@ class Factbase::Query
46
47
  @query = query
47
48
  end
48
49
 
49
- # Iterate them one by one.
50
+ # Iterate facts one by one.
51
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol
50
52
  # @yield [Fact] Facts one-by-one
51
53
  # @return [Integer] Total number of facts yielded
52
- def each
53
- return to_enum(__method__) unless block_given?
54
+ def each(params = {})
55
+ return to_enum(__method__, params) unless block_given?
54
56
  term = Factbase::Syntax.new(@query).to_term
55
57
  yielded = 0
56
58
  @maps.each do |m|
57
59
  extras = {}
58
60
  f = Factbase::Fact.new(@mutex, m)
61
+ params = params.transform_keys(&:to_s) if params.is_a?(Hash)
62
+ f = Factbase::Tee.new(f, params)
59
63
  a = Factbase::Accum.new(f, extras, false)
60
64
  r = term.evaluate(a, @maps)
61
65
  unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
@@ -68,6 +72,19 @@ class Factbase::Query
68
72
  yielded
69
73
  end
70
74
 
75
+ # Read a single value.
76
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol
77
+ # @return The value evaluated
78
+ def one(params = {})
79
+ term = Factbase::Syntax.new(@query).to_term
80
+ params = params.transform_keys(&:to_s) if params.is_a?(Hash)
81
+ r = term.evaluate(Factbase::Tee.new(nil, params), @maps)
82
+ unless %w[String Integer Float Time Array NilClass].include?(r.class.to_s)
83
+ raise "Incorrect type #{r.class} returned by #{@query}"
84
+ end
85
+ r
86
+ end
87
+
71
88
  # Delete all facts that match the query.
72
89
  # @return [Integer] Total number of facts deleted
73
90
  def delete!
@@ -20,6 +20,8 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'decoor'
24
+ require 'others'
23
25
  require_relative '../factbase'
24
26
  require_relative '../factbase/syntax'
25
27
 
@@ -32,16 +34,21 @@ require_relative '../factbase/syntax'
32
34
  # fb = Factabase::Rules.new(fb, '(exists foo)')
33
35
  # fb.txn do |fbt|
34
36
  # f = fbt.insert
35
- # f.bar = 3
37
+ # f.bar = 3 # No exception here
36
38
  # end # Runtime exception here (transaction won't commit)
37
39
  #
38
40
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
39
41
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
40
42
  # License:: MIT
41
43
  class Factbase::Rules
44
+ decoor(:fb)
45
+
42
46
  def initialize(fb, rules, check = Check.new(rules), uid: nil)
47
+ raise 'The "fb" is nil' if fb.nil?
43
48
  @fb = fb
49
+ raise 'The "rules" is nil' if rules.nil?
44
50
  @rules = rules
51
+ raise 'The "check" is nil' if check.nil?
45
52
  @check = check
46
53
  @uid = uid
47
54
  end
@@ -50,10 +57,6 @@ class Factbase::Rules
50
57
  Factbase::Rules.new(@fb.dup, @rules, @check, uid: @uid)
51
58
  end
52
59
 
53
- def size
54
- @fb.size
55
- end
56
-
57
60
  def insert
58
61
  Fact.new(@fb.insert, @check)
59
62
  end
@@ -76,14 +79,6 @@ class Factbase::Rules
76
79
  end
77
80
  end
78
81
 
79
- def export
80
- @fb.export
81
- end
82
-
83
- def import(bytes)
84
- @fb.import(bytes)
85
- end
86
-
87
82
  # Fact decorator.
88
83
  #
89
84
  # This is an internal class, it is not supposed to be instantiated directly.
@@ -94,26 +89,12 @@ class Factbase::Rules
94
89
  @check = check
95
90
  end
96
91
 
97
- def to_s
98
- @fact.to_s
99
- end
100
-
101
- def method_missing(*args)
92
+ others do |*args|
102
93
  r = @fact.method_missing(*args)
103
94
  k = args[0].to_s
104
95
  @check.it(@fact) if k.end_with?('=')
105
96
  r
106
97
  end
107
-
108
- # rubocop:disable Style/OptionalBooleanParameter
109
- def respond_to?(_method, _include_private = false)
110
- # rubocop:enable Style/OptionalBooleanParameter
111
- true
112
- end
113
-
114
- def respond_to_missing?(_method, _include_private = false)
115
- true
116
- end
117
98
  end
118
99
 
119
100
  # Query decorator.
@@ -121,21 +102,19 @@ class Factbase::Rules
121
102
  # This is an internal class, it is not supposed to be instantiated directly.
122
103
  #
123
104
  class Query
105
+ decoor(:query)
106
+
124
107
  def initialize(query, check)
125
108
  @query = query
126
109
  @check = check
127
110
  end
128
111
 
129
- def each
130
- return to_enum(__method__) unless block_given?
112
+ def each(params = {})
113
+ return to_enum(__method__, params) unless block_given?
131
114
  @query.each do |f|
132
115
  yield Fact.new(f, @check)
133
116
  end
134
117
  end
135
-
136
- def delete!
137
- @query.delete!
138
- end
139
118
  end
140
119
 
141
120
  # Check one fact.
data/lib/factbase/tee.rb CHANGED
@@ -20,6 +20,7 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'others'
23
24
  require_relative '../factbase'
24
25
 
25
26
  # Tee of two facts.
@@ -40,18 +41,13 @@ class Factbase::Tee
40
41
  @fact.to_s
41
42
  end
42
43
 
43
- def method_missing(*args)
44
- return @upper[args[1].to_s[1..]] if args[0].to_s == '[]' && args[1].to_s.start_with?('$')
45
- @fact.method_missing(*args)
46
- end
47
-
48
- # rubocop:disable Style/OptionalBooleanParameter
49
- def respond_to?(_method, _include_private = false)
50
- # rubocop:enable Style/OptionalBooleanParameter
51
- true
52
- end
53
-
54
- def respond_to_missing?(_method, _include_private = false)
55
- true
44
+ others do |*args|
45
+ if args[0].to_s == '[]' && args[1].to_s.start_with?('$')
46
+ n = args[1].to_s
47
+ n = n[1..] unless @upper.is_a?(Factbase::Tee)
48
+ @upper[n]
49
+ else
50
+ @fact.method_missing(*args)
51
+ end
56
52
  end
57
53
  end
@@ -81,10 +81,17 @@ module Factbase::Term::Aggregates
81
81
  raise "A term expected, but '#{selector}' provided" unless selector.is_a?(Factbase::Term)
82
82
  term = @operands[1]
83
83
  raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
84
- subset = maps.select { |m| selector.evaluate(Factbase::Tee.new(Factbase::Fact.new(Mutex.new, m), fact), maps) }
84
+ subset = Factbase::Query.new(maps, Mutex.new, selector.to_s).each(fact).to_a
85
85
  term.evaluate(nil, subset)
86
86
  end
87
87
 
88
+ def empty(fact, maps)
89
+ assert_args(1)
90
+ term = @operands[0]
91
+ raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
92
+ Factbase::Query.new(maps, Mutex.new, term.to_s).each(fact).to_a.empty?
93
+ end
94
+
88
95
  def best(maps)
89
96
  k = @operands[0]
90
97
  raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
@@ -39,15 +39,19 @@ module Factbase::Term::Aliases
39
39
 
40
40
  def join(fact, maps)
41
41
  assert_args(2)
42
- mask = @operands[0]
43
- raise "A string expected as first argument of 'join'" unless mask.is_a?(String)
42
+ jumps = @operands[0]
43
+ raise "A string expected as first argument of 'join'" unless jumps.is_a?(String)
44
+ jumps = jumps.split(',')
45
+ .map(&:strip)
46
+ .map { |j| j.split('<=').map(&:strip) }
47
+ .map { |j| j.size == 1 ? [j[0], j[0]] : j }
44
48
  term = @operands[1]
45
49
  raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
46
- subset = maps.select { |m| term.evaluate(Factbase::Tee.new(Factbase::Fact.new(Mutex.new, m), fact), maps) }
47
- subset.each do |m|
48
- m.each do |k, vv|
49
- vv.each do |v|
50
- fact.send("#{mask.gsub('*', k)}=", v)
50
+ subset = Factbase::Query.new(maps, Mutex.new, term.to_s).each(fact).to_a
51
+ subset.each do |s|
52
+ jumps.each do |to, from|
53
+ s[from]&.each do |v|
54
+ fact.send("#{to}=", v)
51
55
  end
52
56
  end
53
57
  end
data/lib/factbase.rb CHANGED
@@ -81,7 +81,7 @@ require 'yaml'
81
81
  # License:: MIT
82
82
  class Factbase
83
83
  # Current version of the gem (changed by .rultor.yml on every release)
84
- VERSION = '0.0.51'
84
+ VERSION = '0.0.53'
85
85
 
86
86
  # An exception that may be thrown in a transaction, to roll it back.
87
87
  class Rollback < StandardError; end
@@ -31,12 +31,12 @@ require_relative '../../../lib/factbase/syntax'
31
31
  class TestAggregates < Minitest::Test
32
32
  def test_aggregation
33
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 }
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
40
  ]
41
41
  {
42
42
  '(eq x (agg (eq y 42) (min x)))' => '(eq x 2)',
@@ -54,6 +54,20 @@ class TestAggregates < Minitest::Test
54
54
  end
55
55
  end
56
56
 
57
+ def test_empty
58
+ maps = [
59
+ { 'x' => [1], 'y' => [0], 'z' => [4] },
60
+ { 'x' => [8], 'y' => [0] }
61
+ ]
62
+ {
63
+ '(empty (eq y 42))' => true,
64
+ '(empty (eq x 1))' => false
65
+ }.each do |q, r|
66
+ t = Factbase::Syntax.new(q).to_term
67
+ assert_equal(r, t.evaluate(nil, maps), q)
68
+ end
69
+ end
70
+
57
71
  private
58
72
 
59
73
  def fact(map = {})
@@ -51,19 +51,22 @@ class TestAliases < Minitest::Test
51
51
 
52
52
  def test_join
53
53
  maps = [
54
- { 'x' => [1], 'y' => [0], 'z' => [4] },
54
+ { 'x' => 1, 'y' => 0, 'z' => 4 },
55
55
  { 'x' => [2], 'bar' => [44, 55, 66] }
56
56
  ]
57
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)'
58
+ '(join "foo_x<=x" (gt x 1))' => '(exists foo_x)',
59
+ '(join "foo <=bar " (exists bar))' => '(and (eq foo 44) (eq foo 55))',
60
+ '(join "uuu" (eq x 1))' => '(absent uuu)',
61
+ '(join "uuu <= fff" (eq fff 1))' => '(absent uuu)'
61
62
  }.each do |q, r|
62
63
  t = Factbase::Syntax.new(q).to_term
63
64
  maps.each do |m|
64
65
  f = Factbase::Accum.new(fact(m), {}, false)
66
+ require_relative '../../../lib/factbase/looged'
67
+ f = Factbase::Looged::Fact.new(f, Loog::NULL)
65
68
  next unless t.evaluate(f, maps)
66
- assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f}")
69
+ assert(Factbase::Syntax.new(r).to_term.evaluate(f, []), "#{q} -> #{f} doesn't match #{r}")
67
70
  end
68
71
  end
69
72
  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)
@@ -110,6 +110,21 @@ class TestQuery < Minitest::Test
110
110
  assert_equal(1, maps.size)
111
111
  end
112
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
+
113
128
  def test_deleting_nothing
114
129
  maps = []
115
130
  maps << { 'foo' => [42] }
@@ -140,6 +155,19 @@ class TestQuery < Minitest::Test
140
155
  assert_equal(1, maps[0].size)
141
156
  end
142
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
+
143
171
  def test_with_nil_alias
144
172
  maps = []
145
173
  maps << { 'foo' => [42] }
@@ -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 42)').each.to_a.size)
104
+ assert_equal(0, fb.query('(eq hello $v)').each(v: 42).to_a.size)
98
105
  end
99
106
  end
@@ -40,4 +40,14 @@ class TestTee < Minitest::Test
40
40
  assert_equal(42, t.foo)
41
41
  assert_equal([13], t['$bar'])
42
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
43
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.51
4
+ version: 0.0.53
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-18 00:00:00.000000000 Z
11
+ date: 2024-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backtrace
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: decoor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: json
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,20 @@ dependencies:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '1.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: others
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: tago
71
99
  requirement: !ruby/object:Gem::Requirement