factbase 0.8.0 → 0.9.0

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +3 -3
  5. data/README.md +24 -24
  6. data/REUSE.toml +7 -2
  7. data/Rakefile +8 -1
  8. data/benchmark/bench_factbase.rb +1 -1
  9. data/fixtures/stories/agg.yml +17 -0
  10. data/fixtures/stories/always.yml +16 -0
  11. data/fixtures/stories/as.yml +16 -0
  12. data/fixtures/stories/count.yml +18 -0
  13. data/fixtures/stories/eq.yml +30 -0
  14. data/fixtures/stories/gt.yml +18 -0
  15. data/fixtures/stories/join.yml +19 -0
  16. data/fixtures/stories/max.yml +14 -0
  17. data/fixtures/stories/min.yml +14 -0
  18. data/fixtures/stories/nth.yml +14 -0
  19. data/fixtures/stories/or.yml +18 -0
  20. data/fixtures/stories/sprintf.yml +12 -0
  21. data/fixtures/stories/sum.yml +14 -0
  22. data/lib/factbase/cached/cached_fact.rb +28 -0
  23. data/lib/factbase/cached/cached_factbase.rb +64 -0
  24. data/lib/factbase/cached/cached_query.rb +61 -0
  25. data/lib/factbase/cached/cached_term.rb +25 -0
  26. data/lib/factbase/fact.rb +13 -13
  27. data/lib/factbase/indexed/indexed_fact.rb +28 -0
  28. data/lib/factbase/indexed/indexed_factbase.rb +64 -0
  29. data/lib/factbase/indexed/indexed_query.rb +56 -0
  30. data/lib/factbase/indexed/indexed_term.rb +60 -0
  31. data/lib/factbase/light.rb +7 -6
  32. data/lib/factbase/logged.rb +67 -44
  33. data/lib/factbase/query.rb +29 -34
  34. data/lib/factbase/rules.rb +15 -14
  35. data/lib/factbase/sync/sync_factbase.rb +57 -0
  36. data/lib/factbase/sync/sync_query.rb +61 -0
  37. data/lib/factbase/syntax.rb +12 -25
  38. data/lib/factbase/tallied.rb +10 -9
  39. data/lib/factbase/taped.rb +8 -0
  40. data/lib/factbase/tee.rb +2 -0
  41. data/lib/factbase/term.rb +45 -17
  42. data/lib/factbase/terms/aggregates.rb +17 -15
  43. data/lib/factbase/terms/aliases.rb +4 -4
  44. data/lib/factbase/terms/casting.rb +8 -8
  45. data/lib/factbase/terms/debug.rb +2 -2
  46. data/lib/factbase/terms/defn.rb +3 -3
  47. data/lib/factbase/terms/logical.rb +53 -14
  48. data/lib/factbase/terms/math.rb +26 -26
  49. data/lib/factbase/terms/meta.rb +14 -14
  50. data/lib/factbase/terms/ordering.rb +4 -4
  51. data/lib/factbase/terms/strings.rb +8 -8
  52. data/lib/factbase/terms/system.rb +3 -3
  53. data/lib/factbase.rb +67 -55
  54. data/test/factbase/cached/test_cached_factbase.rb +22 -0
  55. data/test/factbase/cached/test_cached_query.rb +79 -0
  56. data/test/factbase/indexed/test_indexed_query.rb +175 -0
  57. data/test/factbase/sync/test_sync_query.rb +30 -0
  58. data/test/factbase/terms/test_aggregates.rb +5 -5
  59. data/test/factbase/terms/test_aliases.rb +7 -7
  60. data/test/factbase/terms/test_casting.rb +8 -8
  61. data/test/factbase/terms/test_debug.rb +6 -6
  62. data/test/factbase/terms/test_defn.rb +14 -14
  63. data/test/factbase/terms/test_logical.rb +17 -19
  64. data/test/factbase/terms/test_math.rb +63 -61
  65. data/test/factbase/terms/test_meta.rb +36 -36
  66. data/test/factbase/terms/test_ordering.rb +9 -9
  67. data/test/factbase/terms/test_strings.rb +10 -10
  68. data/test/factbase/terms/test_system.rb +6 -6
  69. data/test/factbase/test_accum.rb +5 -5
  70. data/test/factbase/test_fact.rb +12 -12
  71. data/test/factbase/test_logged.rb +7 -0
  72. data/test/factbase/test_query.rb +99 -37
  73. data/test/factbase/test_rules.rb +1 -1
  74. data/test/factbase/test_syntax.rb +12 -12
  75. data/test/factbase/test_tee.rb +8 -8
  76. data/test/factbase/test_term.rb +39 -30
  77. data/test/test__helper.rb +2 -2
  78. data/test/test_factbase.rb +6 -0
  79. metadata +29 -4
  80. data/lib/factbase/query_once.rb +0 -54
  81. data/lib/factbase/term_once.rb +0 -67
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'others'
7
+ require_relative '../../factbase'
8
+
9
+ # A single fact in a factbase, which is sentitive to changes.
10
+ #
11
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
+ # License:: MIT
14
+ class Factbase::IndexedFact
15
+ # Ctor.
16
+ # @param [Factbase::Fact] origin The original fact
17
+ # @param [Hash] idx The index
18
+ def initialize(origin, idx)
19
+ @origin = origin
20
+ @idx = idx
21
+ end
22
+
23
+ # When a method is missing, this method is called.
24
+ others do |*args|
25
+ @idx.clear if args[0].to_s.end_with?('=')
26
+ @origin.send(*args)
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require 'decoor'
7
+ require_relative '../../factbase'
8
+ require_relative '../../factbase/syntax'
9
+ require_relative 'indexed_fact'
10
+ require_relative 'indexed_query'
11
+ require_relative 'indexed_term'
12
+
13
+ # A factbase with an index.
14
+ #
15
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
16
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
17
+ # License:: MIT
18
+ class Factbase::IndexedFactbase
19
+ decoor(:origin)
20
+
21
+ # Constructor.
22
+ # @param [Factbase] origin Original factbase to decorate
23
+ # @param [Hash] idx Index to use
24
+ def initialize(origin, idx = {})
25
+ raise 'Wront type of original' unless origin.respond_to?(:query)
26
+ @origin = origin
27
+ raise 'Wront type of index' unless idx.is_a?(Hash)
28
+ @idx = idx
29
+ end
30
+
31
+ # Insert a new fact and return it.
32
+ # @return [Factbase::Fact] The fact just inserted
33
+ def insert
34
+ @idx.clear
35
+ Factbase::IndexedFact.new(@origin.insert, @idx)
36
+ end
37
+
38
+ # Convert a query to a term.
39
+ # @param [String] query The query to convert
40
+ # @return [Factbase::Term] The term
41
+ def to_term(query)
42
+ t = @origin.to_term(query)
43
+ t.redress!(Factbase::IndexedTerm, idx: @idx)
44
+ t
45
+ end
46
+
47
+ # Create a query capable of iterating.
48
+ # @param [String] term The term to use
49
+ # @param [Array<Hash>] maps Possible maps to use
50
+ def query(term, maps = nil)
51
+ term = to_term(term) if term.is_a?(String)
52
+ q = @origin.query(term, maps)
53
+ q = Factbase::IndexedQuery.new(q, @idx, self) if term.abstract?
54
+ q
55
+ end
56
+
57
+ # Run an ACID transaction.
58
+ # @return [Factbase::Churn] How many facts have been changed (zero if rolled back)
59
+ def txn
60
+ @origin.txn do |fbt|
61
+ yield Factbase::IndexedFactbase.new(fbt, @idx)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../../factbase'
7
+ require_relative 'indexed_fact'
8
+
9
+ # Query with an index, a decorator of another query.
10
+ #
11
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
+ # License:: MIT
14
+ class Factbase::IndexedQuery
15
+ # Constructor.
16
+ # @param [Factbase::Query] origin Original query
17
+ # @param [Hash] idx The index
18
+ def initialize(origin, idx, fb)
19
+ @origin = origin
20
+ @idx = idx
21
+ @fb = fb
22
+ end
23
+
24
+ # Print it as a string.
25
+ # @return [String] The query as a string
26
+ def to_s
27
+ @origin.to_s
28
+ end
29
+
30
+ # Iterate facts one by one.
31
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol
32
+ # @yield [Fact] Facts one-by-one
33
+ # @return [Integer] Total number of facts yielded
34
+ def each(fb = @fb, params = {})
35
+ return to_enum(__method__, fb, params) unless block_given?
36
+ @origin.each(fb, params) do |f|
37
+ yield Factbase::IndexedFact.new(f, @idx)
38
+ end
39
+ end
40
+
41
+ # Read a single value.
42
+ # @param [Factbase] fb The factbase
43
+ # @param [Hash] params Optional params accessible in the query via the "$" symbol
44
+ # @return [String|Integer|Float|Time|Array|NilClass] The value evaluated
45
+ def one(fb = @fb, params = nil)
46
+ @origin.one(fb, params)
47
+ end
48
+
49
+ # Delete all facts that match the query.
50
+ # @param [Factbase] fb The factbase
51
+ # @return [Integer] Total number of facts deleted
52
+ def delete!(fb = @fb)
53
+ @idx.clear
54
+ @origin.delete!(fb)
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ require_relative '../../factbase'
7
+
8
+ # Term with an index.
9
+ #
10
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
11
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
12
+ # License:: MIT
13
+ module Factbase::IndexedTerm
14
+ def predict(maps, params)
15
+ case @op
16
+ when :eq
17
+ if @operands[0].is_a?(Symbol) && _scalar?(@operands[1])
18
+ key = [maps.object_id, @operands[0], @op]
19
+ if @idx[key].nil?
20
+ @idx[key] = {}
21
+ maps.to_a.each do |m|
22
+ m[@operands[0].to_s]&.each do |v|
23
+ @idx[key][v] = [] if @idx[key][v].nil?
24
+ @idx[key][v].append(m)
25
+ end
26
+ end
27
+ end
28
+ vv =
29
+ if @operands[1].is_a?(Symbol)
30
+ sym = @operands[1].to_s.gsub(/^\$/, '')
31
+ params[sym] || []
32
+ else
33
+ [@operands[1]]
34
+ end
35
+ vv.map { |v| @idx[key][v] || [] }.reduce(&:|)
36
+ else
37
+ maps.to_a
38
+ end
39
+ when :and
40
+ parts = @operands.map { |o| o.predict(maps, params) }
41
+ if parts.include?(nil)
42
+ maps
43
+ else
44
+ parts.reduce(&:&)
45
+ end
46
+ when :or
47
+ @operands.map { |o| o.predict(maps, params) }.reduce(&:|)
48
+ when :join, :as
49
+ nil
50
+ else
51
+ maps.to_a
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def _scalar?(item)
58
+ item.is_a?(String) || item.is_a?(Time) || item.is_a?(Integer) || item.is_a?(Float) || item.is_a?(Symbol)
59
+ end
60
+ end
@@ -11,11 +11,8 @@ require_relative '../factbase'
11
11
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
12
12
  # License:: MIT
13
13
  class Factbase::Light
14
- attr_reader :cache
15
-
16
- def initialize(fb, cache)
14
+ def initialize(fb)
17
15
  @fb = fb
18
- @cache = cache
19
16
  end
20
17
 
21
18
  def size
@@ -26,7 +23,11 @@ class Factbase::Light
26
23
  @fb.insert
27
24
  end
28
25
 
29
- def query(query)
30
- @fb.query(query)
26
+ def to_term(query)
27
+ @fb.to_term(query)
28
+ end
29
+
30
+ def query(query, maps = nil)
31
+ @fb.query(query, maps)
31
32
  end
32
33
  end
@@ -6,20 +6,29 @@
6
6
  require 'decoor'
7
7
  require 'others'
8
8
  require 'time'
9
- require 'loog'
10
9
  require 'tago'
11
10
  require_relative 'syntax'
12
11
 
13
12
  # A decorator of a Factbase, that logs all operations.
13
+ #
14
14
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
15
15
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
16
16
  # License:: MIT
17
17
  class Factbase::Logged
18
- def initialize(fb, loog)
18
+ # Ctor.
19
+ # @param [Factbase] fb The factbase to decorate
20
+ # @param [Object] log The logging facility
21
+ # @param [Integer] time_tolerate How many seconds are OK per request
22
+ # @param [Print] tube The tube to use, if log is NIL
23
+ def initialize(fb, log = nil, time_tolerate: 1, tube: nil)
19
24
  raise 'The "fb" is nil' if fb.nil?
20
25
  @fb = fb
21
- raise 'The "loog" is nil' if loog.nil?
22
- @loog = loog
26
+ if log.nil?
27
+ raise 'Either "log" or "tube" must be non-NIL' if tube.nil?
28
+ @tube = tube
29
+ else
30
+ @tube = Tube.new(log, time_tolerate:)
31
+ end
23
32
  end
24
33
 
25
34
  decoor(:fb)
@@ -27,12 +36,12 @@ class Factbase::Logged
27
36
  def insert
28
37
  start = Time.now
29
38
  f = @fb.insert
30
- @loog.debug("Inserted new fact ##{@fb.size} in #{start.ago}")
31
- Fact.new(f, @loog)
39
+ @tube.say(start, "Inserted new fact ##{@fb.size} in #{start.ago}")
40
+ Fact.new(f, tube: @tube)
32
41
  end
33
42
 
34
43
  def query(query)
35
- Query.new(@fb, query, @loog)
44
+ Query.new(query, @tube, @fb)
36
45
  end
37
46
 
38
47
  def txn
@@ -42,19 +51,36 @@ class Factbase::Logged
42
51
  r =
43
52
  @fb.txn do |fbt|
44
53
  id = fbt.object_id
45
- yield Factbase::Logged.new(fbt, @loog)
54
+ yield Factbase::Logged.new(fbt, tube: @tube)
46
55
  rescue Factbase::Rollback => e
47
56
  rollback = true
48
57
  raise e
49
58
  end
50
59
  if rollback
51
- Factbase::Logged.log_it(@loog, start, "Txn ##{id} rolled back in #{start.ago}")
60
+ @tube.say(start, "Txn ##{id} rolled back in #{start.ago}")
52
61
  else
53
- Factbase::Logged.log_it(@loog, start, "Txn ##{id} touched #{r} in #{start.ago}")
62
+ @tube.say(start, "Txn ##{id} touched #{r} in #{start.ago}")
54
63
  end
55
64
  r
56
65
  end
57
66
 
67
+ # Printer of log messages.
68
+ class Tube
69
+ def initialize(log, time_tolerate: 1)
70
+ @log = log
71
+ @time_tolerate = time_tolerate
72
+ end
73
+
74
+ def say(start, msg)
75
+ m = :debug
76
+ if Time.now - start > @time_tolerate
77
+ msg = "#{msg} (slow!)"
78
+ m = :warn
79
+ end
80
+ @log.send(m, msg)
81
+ end
82
+ end
83
+
58
84
  # Fact decorator.
59
85
  #
60
86
  # This is an internal class, it is not supposed to be instantiated directly.
@@ -62,9 +88,14 @@ class Factbase::Logged
62
88
  class Fact
63
89
  MAX_LENGTH = 64
64
90
 
65
- def initialize(fact, loog)
91
+ def initialize(fact, tube: nil, log: nil)
66
92
  @fact = fact
67
- @loog = loog
93
+ @tube =
94
+ if log.nil?
95
+ tube
96
+ else
97
+ Tube.new(log)
98
+ end
68
99
  end
69
100
 
70
101
  def to_s
@@ -76,13 +107,14 @@ class Factbase::Logged
76
107
  end
77
108
 
78
109
  others do |*args|
110
+ start = Time.now
79
111
  r = @fact.method_missing(*args)
80
112
  k = args[0].to_s
81
113
  v = args[1]
82
114
  s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
83
115
  s = v.to_s.inspect if v.is_a?(String)
84
116
  s = "#{s[0..MAX_LENGTH / 2]}...#{s[-MAX_LENGTH / 2..]}" if s.length > MAX_LENGTH
85
- @loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
117
+ @tube.say(start, "Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
86
118
  r
87
119
  end
88
120
  end
@@ -92,76 +124,76 @@ class Factbase::Logged
92
124
  # This is an internal class, it is not supposed to be instantiated directly.
93
125
  #
94
126
  class Query
95
- def initialize(fb, expr, loog)
96
- @fb = fb
127
+ def initialize(expr, tube, fb)
97
128
  @expr = expr
98
- @loog = loog
129
+ @tube = tube
130
+ @fb = fb
99
131
  end
100
132
 
101
- def one(params = {})
133
+ def one(fb = @fb, params = {})
102
134
  start = Time.now
103
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
135
+ q = Factbase::Syntax.new(@expr).to_term.to_s
104
136
  r = nil
105
137
  tail =
106
138
  Factbase::Logged.elapsed do
107
- r = @fb.query(@expr).one(params)
139
+ r = fb.query(@expr).one(fb, params)
108
140
  end
109
141
  if r.nil?
110
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
142
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
111
143
  else
112
- Factbase::Logged.log_it(@loog, start, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
144
+ @tube.say(start, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
113
145
  end
114
146
  r
115
147
  end
116
148
 
117
- def each(params = {}, &)
149
+ def each(fb = @fb, params = {}, &)
118
150
  start = Time.now
119
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
151
+ q = Factbase::Syntax.new(@expr).to_term.to_s
120
152
  if block_given?
121
153
  r = nil
122
154
  tail =
123
155
  Factbase::Logged.elapsed do
124
- r = @fb.query(@expr).each(params, &)
156
+ r = fb.query(@expr).each(fb, params, &)
125
157
  end
126
158
  raise ".each of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
127
159
  if r.zero?
128
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
160
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
129
161
  else
130
- Factbase::Logged.log_it(@loog, start, "Found #{r} fact(s) by '#{q}' #{tail}")
162
+ @tube.say(start, "Found #{r} fact(s) by '#{q}' #{tail}")
131
163
  end
132
164
  r
133
165
  else
134
166
  array = []
135
167
  tail =
136
168
  Factbase::Logged.elapsed do
137
- @fb.query(@expr).each(params) do |f|
169
+ fb.query(@expr).each(fb, params) do |f|
138
170
  array << f
139
171
  end
140
172
  end
141
173
  if array.empty?
142
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
174
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
143
175
  else
144
- Factbase::Logged.log_it(@loog, start, "Found #{array.size} fact(s) by '#{q}' #{tail}")
176
+ @tube.say(start, "Found #{array.size} fact(s) by '#{q}' #{tail}")
145
177
  end
146
178
  array
147
179
  end
148
180
  end
149
181
 
150
- def delete!
182
+ def delete!(fb = @fb)
151
183
  r = nil
152
184
  start = Time.now
153
- before = @fb.size
185
+ before = fb.size
154
186
  tail =
155
187
  Factbase::Logged.elapsed do
156
- r = @fb.query(@expr).delete!
188
+ r = @fb.query(@expr).delete!(fb)
157
189
  end
158
190
  raise ".delete! of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
159
191
  if before.zero?
160
- Factbase::Logged.log_it(@loog, start, "There were no facts, nothing deleted by '#{@expr}' #{tail}")
192
+ @tube.say(start, "There were no facts, nothing deleted by '#{@expr}' #{tail}")
161
193
  elsif r.zero?
162
- Factbase::Logged.log_it(@loog, start, "No facts out of #{before} deleted by '#{@expr}' #{tail}")
194
+ @tube.say(start, "No facts out of #{before} deleted by '#{@expr}' #{tail}")
163
195
  else
164
- Factbase::Logged.log_it(@loog, start, "Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
196
+ @tube.say(start, "Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
165
197
  end
166
198
  r
167
199
  end
@@ -172,13 +204,4 @@ class Factbase::Logged
172
204
  yield
173
205
  "in #{start.ago}"
174
206
  end
175
-
176
- def self.log_it(loog, start, msg)
177
- m = :debug
178
- if Time.now - start > 1
179
- msg = "#{msg} (slow!)"
180
- m = :warn
181
- end
182
- loog.send(m, msg)
183
- end
184
207
  end
@@ -4,9 +4,9 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require_relative '../factbase'
7
- require_relative 'syntax'
8
- require_relative 'fact'
9
7
  require_relative 'accum'
8
+ require_relative 'fact'
9
+ require_relative 'syntax'
10
10
  require_relative 'tee'
11
11
 
12
12
  # Query.
@@ -21,40 +21,37 @@ require_relative 'tee'
21
21
  # License:: MIT
22
22
  class Factbase::Query
23
23
  # Constructor.
24
- # @param [Factbase] fb Factbase
25
24
  # @param [Array<Fact>] maps Array of facts to start with
26
- # @param [Mutex] mutex Mutex to sync all modifications to the +maps+
27
- # @param [String] query The query as a string
28
- def initialize(fb, maps, mutex, query)
29
- @fb = fb
25
+ # @param [String|Factbase::Term] term The query term
26
+ def initialize(maps, term, fb)
30
27
  @maps = maps
31
- @mutex = mutex
32
- @query = query
28
+ @term = term.is_a?(String) ? Factbase::Syntax.new(term).to_term : term
29
+ @fb = fb
33
30
  end
34
31
 
35
32
  # Print it as a string.
36
33
  # @return [String] The query as a string
37
34
  def to_s
38
- @query.to_s
35
+ @term.to_s
39
36
  end
40
37
 
41
38
  # Iterate facts one by one.
39
+ # @param [Factbase] fb The factbase
42
40
  # @param [Hash] params Optional params accessible in the query via the "$" symbol
43
41
  # @yield [Fact] Facts one-by-one
44
42
  # @return [Integer] Total number of facts yielded
45
- def each(params = {})
46
- return to_enum(__method__, params) unless block_given?
47
- term = Factbase::Syntax.new(@fb, @query).to_term
43
+ def each(fb = @fb, params = {})
44
+ return to_enum(__method__, fb, params) unless block_given?
48
45
  yielded = 0
49
- @maps.each do |m|
46
+ params = params.transform_keys(&:to_s) if params.is_a?(Hash)
47
+ (@term.predict(@maps, params) || @maps).each do |m|
50
48
  extras = {}
51
- f = Factbase::Fact.new(@fb, @mutex, m)
52
- params = params.transform_keys(&:to_s) if params.is_a?(Hash)
49
+ f = Factbase::Fact.new(m)
53
50
  f = Factbase::Tee.new(f, params)
54
51
  a = Factbase::Accum.new(f, extras, false)
55
- r = term.evaluate(a, @maps)
52
+ r = @term.evaluate(a, @maps, fb)
56
53
  unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
57
- raise "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@query.inspect}"
54
+ raise "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@term.inspect}"
58
55
  end
59
56
  next unless r
60
57
  yield Factbase::Accum.new(f, extras, true)
@@ -64,32 +61,30 @@ class Factbase::Query
64
61
  end
65
62
 
66
63
  # Read a single value.
64
+ # @param [Factbase] fb The factbase
67
65
  # @param [Hash] params Optional params accessible in the query via the "$" symbol
68
- # @return The value evaluated
69
- def one(params = {})
70
- term = Factbase::Syntax.new(@fb, @query).to_term
66
+ # @return [String|Integer|Float|Time|Array|NilClass] The value evaluated
67
+ def one(fb = @fb, params = {})
71
68
  params = params.transform_keys(&:to_s) if params.is_a?(Hash)
72
- r = term.evaluate(Factbase::Tee.new(nil, params), @maps)
69
+ r = @term.evaluate(Factbase::Tee.new(Factbase::Fact.new({}), params), @maps, fb)
73
70
  unless %w[String Integer Float Time Array NilClass].include?(r.class.to_s)
74
- raise "Incorrect type #{r.class} returned by #{@query.inspect}"
71
+ raise "Incorrect type #{r.class} returned by #{@term.inspect}"
75
72
  end
76
73
  r
77
74
  end
78
75
 
79
76
  # Delete all facts that match the query.
77
+ # @param [Factbase] fb The factbase to delete from
80
78
  # @return [Integer] Total number of facts deleted
81
- def delete!
82
- term = Factbase::Syntax.new(@fb, @query).to_term
79
+ def delete!(fb = @fb)
83
80
  deleted = 0
84
- @mutex.synchronize do
85
- @maps.delete_if do |m|
86
- f = Factbase::Fact.new(@fb, @mutex, m)
87
- if term.evaluate(f, @maps)
88
- deleted += 1
89
- true
90
- else
91
- false
92
- end
81
+ @maps.delete_if do |m|
82
+ f = Factbase::Fact.new(m)
83
+ if @term.evaluate(f, @maps, fb)
84
+ deleted += 1
85
+ true
86
+ else
87
+ false
93
88
  end
94
89
  end
95
90
  deleted
@@ -37,11 +37,11 @@ class Factbase::Rules
37
37
  end
38
38
 
39
39
  def insert
40
- Fact.new(@fb.insert, @check)
40
+ Fact.new(@fb.insert, @check, @fb)
41
41
  end
42
42
 
43
43
  def query(query)
44
- Query.new(@fb.query(query), @check)
44
+ Query.new(@fb.query(query), @check, @fb)
45
45
  end
46
46
 
47
47
  def txn
@@ -53,7 +53,7 @@ class Factbase::Rules
53
53
  @check = before
54
54
  fbt.query('(always)').each do |f|
55
55
  next unless later.include?(f)
56
- @check.it(f)
56
+ @check.it(f, @fb)
57
57
  end
58
58
  end
59
59
  end
@@ -63,9 +63,10 @@ class Factbase::Rules
63
63
  # This is an internal class, it is not supposed to be instantiated directly.
64
64
  #
65
65
  class Fact
66
- def initialize(fact, check)
66
+ def initialize(fact, check, fb)
67
67
  @fact = fact
68
68
  @check = check
69
+ @fb = fb
69
70
  end
70
71
 
71
72
  def to_s
@@ -79,7 +80,7 @@ class Factbase::Rules
79
80
  others do |*args|
80
81
  r = @fact.method_missing(*args)
81
82
  k = args[0].to_s
82
- @check.it(@fact) if k.end_with?('=')
83
+ @check.it(@fact, @fb) if k.end_with?('=')
83
84
  r
84
85
  end
85
86
  end
@@ -87,19 +88,19 @@ class Factbase::Rules
87
88
  # Query decorator.
88
89
  #
89
90
  # This is an internal class, it is not supposed to be instantiated directly.
90
- #
91
91
  class Query
92
92
  decoor(:query)
93
93
 
94
- def initialize(query, check)
94
+ def initialize(query, check, fb)
95
95
  @query = query
96
96
  @check = check
97
+ @fb = fb
97
98
  end
98
99
 
99
- def each(params = {})
100
- return to_enum(__method__, params) unless block_given?
101
- @query.each do |f|
102
- yield Fact.new(f, @check)
100
+ def each(fb = @fb, params = {})
101
+ return to_enum(__method__, fb, params) unless block_given?
102
+ @query.each(fb, params) do |f|
103
+ yield Fact.new(f, @check, fb)
103
104
  end
104
105
  end
105
106
  end
@@ -112,8 +113,8 @@ class Factbase::Rules
112
113
  @expr = expr
113
114
  end
114
115
 
115
- def it(fact)
116
- return if Factbase::Syntax.new(Factbase.new, @expr).to_term.evaluate(fact, [])
116
+ def it(fact, fb)
117
+ return if Factbase::Syntax.new(@expr).to_term.evaluate(fact, [], fb)
117
118
  e = "#{@expr[0..32]}..." if @expr.length > 32
118
119
  raise "The fact doesn't match the #{e.inspect} rule: #{fact}"
119
120
  end
@@ -128,7 +129,7 @@ class Factbase::Rules
128
129
  @facts = Set.new
129
130
  end
130
131
 
131
- def it(fact)
132
+ def it(fact, _fb)
132
133
  a = fact[@uid]
133
134
  return if a.nil?
134
135
  @facts << a[0] unless @uid.nil?