factbase 0.8.0 → 0.9.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +13 -13
  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/inv.rb +18 -6
  32. data/lib/factbase/light.rb +7 -6
  33. data/lib/factbase/logged.rb +87 -62
  34. data/lib/factbase/query.rb +29 -34
  35. data/lib/factbase/rules.rb +16 -15
  36. data/lib/factbase/sync/sync_factbase.rb +57 -0
  37. data/lib/factbase/sync/sync_query.rb +61 -0
  38. data/lib/factbase/syntax.rb +12 -25
  39. data/lib/factbase/tallied.rb +11 -10
  40. data/lib/factbase/taped.rb +8 -0
  41. data/lib/factbase/tee.rb +2 -0
  42. data/lib/factbase/term.rb +45 -17
  43. data/lib/factbase/terms/aggregates.rb +17 -15
  44. data/lib/factbase/terms/aliases.rb +4 -4
  45. data/lib/factbase/terms/casting.rb +8 -8
  46. data/lib/factbase/terms/debug.rb +2 -2
  47. data/lib/factbase/terms/defn.rb +3 -3
  48. data/lib/factbase/terms/logical.rb +53 -14
  49. data/lib/factbase/terms/math.rb +26 -26
  50. data/lib/factbase/terms/meta.rb +14 -14
  51. data/lib/factbase/terms/ordering.rb +4 -4
  52. data/lib/factbase/terms/strings.rb +8 -8
  53. data/lib/factbase/terms/system.rb +3 -3
  54. data/lib/factbase.rb +67 -55
  55. data/test/factbase/cached/test_cached_factbase.rb +22 -0
  56. data/test/factbase/cached/test_cached_query.rb +79 -0
  57. data/test/factbase/indexed/test_indexed_query.rb +175 -0
  58. data/test/factbase/sync/test_sync_query.rb +30 -0
  59. data/test/factbase/terms/test_aggregates.rb +5 -5
  60. data/test/factbase/terms/test_aliases.rb +7 -7
  61. data/test/factbase/terms/test_casting.rb +8 -8
  62. data/test/factbase/terms/test_debug.rb +6 -6
  63. data/test/factbase/terms/test_defn.rb +14 -14
  64. data/test/factbase/terms/test_logical.rb +17 -19
  65. data/test/factbase/terms/test_math.rb +63 -61
  66. data/test/factbase/terms/test_meta.rb +36 -36
  67. data/test/factbase/terms/test_ordering.rb +9 -9
  68. data/test/factbase/terms/test_strings.rb +10 -10
  69. data/test/factbase/terms/test_system.rb +6 -6
  70. data/test/factbase/test_accum.rb +5 -5
  71. data/test/factbase/test_fact.rb +12 -12
  72. data/test/factbase/test_logged.rb +7 -0
  73. data/test/factbase/test_query.rb +110 -37
  74. data/test/factbase/test_rules.rb +1 -1
  75. data/test/factbase/test_syntax.rb +12 -12
  76. data/test/factbase/test_tee.rb +8 -8
  77. data/test/factbase/test_term.rb +39 -30
  78. data/test/test__helper.rb +2 -2
  79. data/test/test_factbase.rb +6 -0
  80. metadata +29 -4
  81. data/lib/factbase/query_once.rb +0 -54
  82. 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
data/lib/factbase/inv.rb CHANGED
@@ -8,6 +8,17 @@ require 'decoor'
8
8
  require_relative '../factbase'
9
9
 
10
10
  # A decorator of a Factbase, that checks invariants on every set.
11
+ #
12
+ # For example, you can use this decorator if you want to check that every
13
+ # fact has +when+:
14
+ #
15
+ # fb = Factbase::Inv.new(Factbase.new) do |f, fbt|
16
+ # assert !f['when'].nil?
17
+ # end
18
+ #
19
+ # The second argument passed to the block is the factbase, while the first
20
+ # one is the fact just touched.
21
+ #
11
22
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
12
23
  # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
13
24
  # License:: MIT
@@ -23,8 +34,8 @@ class Factbase::Inv
23
34
  Fact.new(@fb.insert, @block)
24
35
  end
25
36
 
26
- def query(query)
27
- Query.new(@fb.query(query), @block)
37
+ def query(query, maps = nil)
38
+ Query.new(@fb.query(query, maps), @block, self)
28
39
  end
29
40
 
30
41
  def txn
@@ -65,14 +76,15 @@ class Factbase::Inv
65
76
  class Query
66
77
  decoor(:query)
67
78
 
68
- def initialize(query, block)
79
+ def initialize(query, block, fb)
69
80
  @query = query
70
81
  @block = block
82
+ @fb = fb
71
83
  end
72
84
 
73
- def each
74
- return to_enum(__method__) unless block_given?
75
- @query.each do |f|
85
+ def each(fb = @fb, params = {})
86
+ return to_enum(__method__, fb, params) unless block_given?
87
+ @query.each(fb, params) do |f|
76
88
  yield Fact.new(f, @block)
77
89
  end
78
90
  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,33 +6,43 @@
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
- @fb = fb
21
- raise 'The "loog" is nil' if loog.nil?
22
- @loog = loog
25
+ @origin = fb
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
- decoor(:fb)
34
+ decoor(:origin)
26
35
 
27
36
  def insert
28
37
  start = Time.now
29
- f = @fb.insert
30
- @loog.debug("Inserted new fact ##{@fb.size} in #{start.ago}")
31
- Fact.new(f, @loog)
38
+ f = @origin.insert
39
+ @tube.say(start, "Inserted new fact ##{@origin.size} in #{start.ago}")
40
+ Fact.new(f, tube: @tube)
32
41
  end
33
42
 
34
- def query(query)
35
- Query.new(@fb, query, @loog)
43
+ def query(term, maps = nil)
44
+ term = to_term(term) if term.is_a?(String)
45
+ Query.new(term, maps, @tube, @origin)
36
46
  end
37
47
 
38
48
  def txn
@@ -40,21 +50,38 @@ class Factbase::Logged
40
50
  id = nil
41
51
  rollback = false
42
52
  r =
43
- @fb.txn do |fbt|
53
+ @origin.txn do |fbt|
44
54
  id = fbt.object_id
45
- yield Factbase::Logged.new(fbt, @loog)
55
+ yield Factbase::Logged.new(fbt, tube: @tube)
46
56
  rescue Factbase::Rollback => e
47
57
  rollback = true
48
58
  raise e
49
59
  end
50
60
  if rollback
51
- Factbase::Logged.log_it(@loog, start, "Txn ##{id} rolled back in #{start.ago}")
61
+ @tube.say(start, "Txn ##{id} rolled back in #{start.ago}")
52
62
  else
53
- Factbase::Logged.log_it(@loog, start, "Txn ##{id} touched #{r} in #{start.ago}")
63
+ @tube.say(start, "Txn ##{id} touched #{r} in #{start.ago}")
54
64
  end
55
65
  r
56
66
  end
57
67
 
68
+ # Printer of log messages.
69
+ class Tube
70
+ def initialize(log, time_tolerate: 1)
71
+ @log = log
72
+ @time_tolerate = time_tolerate
73
+ end
74
+
75
+ def say(start, msg)
76
+ m = :debug
77
+ if Time.now - start > @time_tolerate
78
+ msg = "#{msg} (slow!)"
79
+ m = :warn
80
+ end
81
+ @log.send(m, msg)
82
+ end
83
+ end
84
+
58
85
  # Fact decorator.
59
86
  #
60
87
  # This is an internal class, it is not supposed to be instantiated directly.
@@ -62,9 +89,14 @@ class Factbase::Logged
62
89
  class Fact
63
90
  MAX_LENGTH = 64
64
91
 
65
- def initialize(fact, loog)
92
+ def initialize(fact, tube: nil, log: nil)
66
93
  @fact = fact
67
- @loog = loog
94
+ @tube =
95
+ if log.nil?
96
+ tube
97
+ else
98
+ Tube.new(log)
99
+ end
68
100
  end
69
101
 
70
102
  def to_s
@@ -76,13 +108,14 @@ class Factbase::Logged
76
108
  end
77
109
 
78
110
  others do |*args|
111
+ start = Time.now
79
112
  r = @fact.method_missing(*args)
80
113
  k = args[0].to_s
81
114
  v = args[1]
82
115
  s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
83
116
  s = v.to_s.inspect if v.is_a?(String)
84
117
  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?('=')
118
+ @tube.say(start, "Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
86
119
  r
87
120
  end
88
121
  end
@@ -90,78 +123,79 @@ class Factbase::Logged
90
123
  # Query decorator.
91
124
  #
92
125
  # This is an internal class, it is not supposed to be instantiated directly.
93
- #
94
126
  class Query
95
- def initialize(fb, expr, loog)
127
+ def initialize(term, maps, tube, fb)
128
+ @term = term
129
+ @maps = maps
130
+ @tube = tube
96
131
  @fb = fb
97
- @expr = expr
98
- @loog = loog
99
132
  end
100
133
 
101
- def one(params = {})
134
+ def each(fb = nil, params = {}, &)
135
+ return to_enum(__method__, fb, params) unless block_given?
102
136
  start = Time.now
103
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
104
- r = nil
105
- tail =
106
- Factbase::Logged.elapsed do
107
- r = @fb.query(@expr).one(params)
108
- end
109
- if r.nil?
110
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
111
- else
112
- Factbase::Logged.log_it(@loog, start, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
113
- end
114
- r
115
- end
116
-
117
- def each(params = {}, &)
118
- start = Time.now
119
- q = Factbase::Syntax.new(@fb, @expr).to_term.to_s
137
+ q = Factbase::Syntax.new(@term).to_term.to_s
120
138
  if block_given?
121
139
  r = nil
122
140
  tail =
123
141
  Factbase::Logged.elapsed do
124
- r = @fb.query(@expr).each(params, &)
142
+ r = @fb.query(@term, @maps).each(@fb, params, &)
125
143
  end
126
- raise ".each of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
144
+ raise ".each of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
127
145
  if r.zero?
128
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
146
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
129
147
  else
130
- Factbase::Logged.log_it(@loog, start, "Found #{r} fact(s) by '#{q}' #{tail}")
148
+ @tube.say(start, "Found #{r} fact(s) by '#{q}' #{tail}")
131
149
  end
132
150
  r
133
151
  else
134
152
  array = []
135
153
  tail =
136
154
  Factbase::Logged.elapsed do
137
- @fb.query(@expr).each(params) do |f|
155
+ @fb.query(@term, @maps).each(@fb, params) do |f|
138
156
  array << f
139
157
  end
140
158
  end
141
159
  if array.empty?
142
- Factbase::Logged.log_it(@loog, start, "Nothing found by '#{q}' #{tail}")
160
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
143
161
  else
144
- Factbase::Logged.log_it(@loog, start, "Found #{array.size} fact(s) by '#{q}' #{tail}")
162
+ @tube.say(start, "Found #{array.size} fact(s) by '#{q}' #{tail}")
145
163
  end
146
164
  array
147
165
  end
148
166
  end
149
167
 
150
- def delete!
168
+ def one(_fb = nil, params = {})
169
+ start = Time.now
170
+ q = Factbase::Syntax.new(@term).to_term.to_s
171
+ r = nil
172
+ tail =
173
+ Factbase::Logged.elapsed do
174
+ r = @fb.query(@term, @maps).one(@fb, params)
175
+ end
176
+ if r.nil?
177
+ @tube.say(start, "Nothing found by '#{q}' #{tail}")
178
+ else
179
+ @tube.say(start, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
180
+ end
181
+ r
182
+ end
183
+
184
+ def delete!(_fb = nil)
151
185
  r = nil
152
186
  start = Time.now
153
187
  before = @fb.size
154
188
  tail =
155
189
  Factbase::Logged.elapsed do
156
- r = @fb.query(@expr).delete!
190
+ r = @fb.query(@term).delete!(@fb)
157
191
  end
158
- raise ".delete! of #{@expr.class} returned #{r.class}" unless r.is_a?(Integer)
192
+ raise ".delete! of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
159
193
  if before.zero?
160
- Factbase::Logged.log_it(@loog, start, "There were no facts, nothing deleted by '#{@expr}' #{tail}")
194
+ @tube.say(start, "There were no facts, nothing deleted by '#{@term}' #{tail}")
161
195
  elsif r.zero?
162
- Factbase::Logged.log_it(@loog, start, "No facts out of #{before} deleted by '#{@expr}' #{tail}")
196
+ @tube.say(start, "No facts out of #{before} deleted by '#{@term}' #{tail}")
163
197
  else
164
- Factbase::Logged.log_it(@loog, start, "Deleted #{r} fact(s) out of #{before} by '#{@expr}' #{tail}")
198
+ @tube.say(start, "Deleted #{r} fact(s) out of #{before} by '#{@term}' #{tail}")
165
199
  end
166
200
  r
167
201
  end
@@ -172,13 +206,4 @@ class Factbase::Logged
172
206
  yield
173
207
  "in #{start.ago}"
174
208
  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
209
  end