factbase 0.7.5 → 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 -27
  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 +70 -35
  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 +16 -26
  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 +24 -11
  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
data/lib/factbase/fact.rb CHANGED
@@ -10,28 +10,27 @@ require_relative '../factbase'
10
10
 
11
11
  # A single fact in a factbase.
12
12
  #
13
- # This is an internal class, it is not supposed to be instantiated directly,
14
- # by the +Factbase+ class.
13
+ # This is an internal class, it is supposed to be instantiated only by the
14
+ # +Factbase+ class.
15
15
  # However, it is possible to use it for testing directly, for example to make a
16
16
  # fact with a single key/value pair inside:
17
17
  #
18
18
  # require 'factbase/fact'
19
- # f = Factbase::Fact.new(Factbase.new, Mutex.new, { 'foo' => [42, 256, 'Hello, world!'] })
19
+ # f = Factbase::Fact.new({ 'foo' => [42, 256, 'Hello, world!'] })
20
20
  # assert_equal(42, f.foo)
21
21
  #
22
22
  # A fact is basically a key/value hash map, where values are non-empty
23
23
  # sets of values.
24
24
  #
25
+ # It is NOT thread-safe!
26
+ #
25
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
28
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
27
29
  # License:: MIT
28
30
  class Factbase::Fact
29
31
  # Ctor.
30
- # @param [Mutex] mutex A mutex to use for maps synchronization
31
32
  # @param [Hash] map A map of key/value pairs
32
- def initialize(fb, mutex, map)
33
- @fb = fb
34
- @mutex = mutex
33
+ def initialize(map)
35
34
  @map = map
36
35
  end
37
36
 
@@ -48,6 +47,10 @@ class Factbase::Fact
48
47
  end
49
48
 
50
49
  # When a method is missing, this method is called.
50
+ # Method missing handler for dynamic property access and setting
51
+ # @param [Symbol] method The method name being called
52
+ # @param [Array] args Method arguments
53
+ # @return [Object] The value retrieved or nil if setting a value
51
54
  others do |*args|
52
55
  k = args[0].to_s
53
56
  if k.end_with?('=')
@@ -59,12 +62,9 @@ class Factbase::Fact
59
62
  raise "The value of '#{kk}' can't be empty" if v == ''
60
63
  raise "The type '#{v.class}' of '#{kk}' is not allowed" unless [String, Integer, Float, Time].include?(v.class)
61
64
  v = v.utc if v.is_a?(Time)
62
- @mutex.synchronize do
63
- @map[kk] = [] if @map[kk].nil?
64
- @map[kk] << v
65
- @map[kk].uniq!
66
- end
67
- @fb.cache.clear
65
+ @map[kk] = [] if @map[kk].nil?
66
+ @map[kk] << v
67
+ @map[kk].uniq!
68
68
  nil
69
69
  elsif k == '[]'
70
70
  @map[args[1].to_s]
@@ -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
- @loog.debug("Txn ##{id} rolled back in #{start.ago}")
60
+ @tube.say(start, "Txn ##{id} rolled back in #{start.ago}")
52
61
  else
53
- @loog.debug("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,73 +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 = {})
102
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
133
+ def one(fb = @fb, params = {})
134
+ start = Time.now
135
+ q = Factbase::Syntax.new(@expr).to_term.to_s
103
136
  r = nil
104
137
  tail =
105
138
  Factbase::Logged.elapsed do
106
- r = @fb.query(@expr).one(params)
139
+ r = fb.query(@expr).one(fb, params)
107
140
  end
108
141
  if r.nil?
109
- @loog.debug("Nothing found by '#{q}' #{tail}")
142
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
110
143
  else
111
- @loog.debug("Found #{r} (#{r.class}) by '#{q}' #{tail}")
144
+ @tube.say(start, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
112
145
  end
113
146
  r
114
147
  end
115
148
 
116
- def each(params = {}, &)
117
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
149
+ def each(fb = @fb, params = {}, &)
150
+ start = Time.now
151
+ q = Factbase::Syntax.new(@expr).to_term.to_s
118
152
  if block_given?
119
153
  r = nil
120
154
  tail =
121
155
  Factbase::Logged.elapsed do
122
- r = @fb.query(@expr).each(params, &)
156
+ r = fb.query(@expr).each(fb, params, &)
123
157
  end
124
158
  raise ".each of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
125
159
  if r.zero?
126
- @loog.debug("Nothing found by '#{q}' #{tail}")
160
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
127
161
  else
128
- @loog.debug("Found #{r} fact(s) by '#{q}' #{tail}")
162
+ @tube.say(start, "Found #{r} fact(s) by '#{q}' #{tail}")
129
163
  end
130
164
  r
131
165
  else
132
166
  array = []
133
167
  tail =
134
168
  Factbase::Logged.elapsed do
135
- @fb.query(@expr).each(params) do |f|
169
+ fb.query(@expr).each(fb, params) do |f|
136
170
  array << f
137
171
  end
138
172
  end
139
173
  if array.empty?
140
- @loog.debug("Nothing found by '#{q}' #{tail}")
174
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
141
175
  else
142
- @loog.debug("Found #{array.size} fact(s) by '#{q}' #{tail}")
176
+ @tube.say(start, "Found #{array.size} fact(s) by '#{q}' #{tail}")
143
177
  end
144
178
  array
145
179
  end
146
180
  end
147
181
 
148
- def delete!
182
+ def delete!(fb = @fb)
149
183
  r = nil
150
- before = @fb.size
184
+ start = Time.now
185
+ before = fb.size
151
186
  tail =
152
187
  Factbase::Logged.elapsed do
153
- r = @fb.query(@expr).delete!
188
+ r = @fb.query(@expr).delete!(fb)
154
189
  end
155
190
  raise ".delete! of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
156
191
  if before.zero?
157
- @loog.debug("There were no facts, nothing deleted by '#{@expr}' #{tail}")
192
+ @tube.say(start, "There were no facts, nothing deleted by '#{@expr}' #{tail}")
158
193
  elsif r.zero?
159
- @loog.debug("No facts out of #{before} deleted by '#{@expr}' #{tail}")
194
+ @tube.say(start, "No facts out of #{before} deleted by '#{@expr}' #{tail}")
160
195
  else
161
- @loog.debug("Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
196
+ @tube.say(start, "Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
162
197
  end
163
198
  r
164
199
  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