factbase 0.19.11 → 0.19.12
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.
- checksums.yaml +4 -4
- data/Gemfile +5 -4
- data/Gemfile.lock +16 -12
- data/README.md +49 -18
- data/Rakefile +2 -7
- data/factbase.gemspec +11 -11
- data/lib/factbase/accum.rb +1 -1
- data/lib/factbase/cached/cached_fact.rb +1 -2
- data/lib/factbase/cached/cached_factbase.rb +3 -3
- data/lib/factbase/cached/cached_query.rb +4 -6
- data/lib/factbase/cached/cached_term.rb +1 -2
- data/lib/factbase/churn.rb +4 -8
- data/lib/factbase/fact.rb +12 -9
- data/lib/factbase/flatten.rb +2 -2
- data/lib/factbase/impatient.rb +14 -13
- data/lib/factbase/indexed/indexed_and.rb +14 -20
- data/lib/factbase/indexed/indexed_eq.rb +5 -1
- data/lib/factbase/indexed/indexed_fact.rb +1 -4
- data/lib/factbase/indexed/indexed_factbase.rb +4 -4
- data/lib/factbase/indexed/indexed_gt.rb +3 -1
- data/lib/factbase/indexed/indexed_gte.rb +51 -0
- data/lib/factbase/indexed/indexed_lt.rb +3 -1
- data/lib/factbase/indexed/indexed_lte.rb +51 -0
- data/lib/factbase/indexed/indexed_not.rb +1 -1
- data/lib/factbase/indexed/indexed_or.rb +2 -2
- data/lib/factbase/indexed/indexed_query.rb +6 -7
- data/lib/factbase/indexed/indexed_term.rb +10 -6
- data/lib/factbase/indexed/indexed_unique.rb +4 -2
- data/lib/factbase/inv.rb +3 -3
- data/lib/factbase/lazy_taped.rb +10 -13
- data/lib/factbase/lazy_taped_hash.rb +2 -1
- data/lib/factbase/light.rb +1 -1
- data/lib/factbase/logged.rb +37 -34
- data/lib/factbase/pre.rb +3 -3
- data/lib/factbase/query.rb +4 -5
- data/lib/factbase/rules.rb +8 -8
- data/lib/factbase/sync/sync_factbase.rb +2 -2
- data/lib/factbase/syntax.rb +18 -19
- data/lib/factbase/tallied.rb +6 -7
- data/lib/factbase/taped.rb +5 -11
- data/lib/factbase/tee.rb +2 -2
- data/lib/factbase/term.rb +53 -60
- data/lib/factbase/terms/agg.rb +3 -4
- data/lib/factbase/terms/arithmetic.rb +7 -7
- data/lib/factbase/terms/as.rb +2 -2
- data/lib/factbase/terms/assert.rb +5 -13
- data/lib/factbase/terms/base.rb +6 -7
- data/lib/factbase/terms/best.rb +1 -1
- data/lib/factbase/terms/boolean.rb +1 -1
- data/lib/factbase/terms/compare.rb +2 -1
- data/lib/factbase/terms/defn.rb +8 -6
- data/lib/factbase/terms/empty.rb +1 -1
- data/lib/factbase/terms/first.rb +2 -2
- data/lib/factbase/terms/head.rb +3 -3
- data/lib/factbase/terms/inverted.rb +2 -2
- data/lib/factbase/terms/join.rb +8 -7
- data/lib/factbase/terms/matches.rb +4 -4
- data/lib/factbase/terms/max.rb +1 -1
- data/lib/factbase/terms/min.rb +1 -1
- data/lib/factbase/terms/nth.rb +3 -3
- data/lib/factbase/terms/plus.rb +1 -1
- data/lib/factbase/terms/prev.rb +3 -6
- data/lib/factbase/terms/sorted.rb +2 -2
- data/lib/factbase/terms/sprintf.rb +5 -4
- data/lib/factbase/terms/sum.rb +1 -1
- data/lib/factbase/terms/to_float.rb +2 -2
- data/lib/factbase/terms/to_integer.rb +2 -2
- data/lib/factbase/terms/to_string.rb +1 -1
- data/lib/factbase/terms/to_time.rb +2 -2
- data/lib/factbase/terms/traced.rb +2 -2
- data/lib/factbase/terms/undef.rb +2 -2
- data/lib/factbase/terms/unique.rb +3 -7
- data/lib/factbase/to_json.rb +1 -1
- data/lib/factbase/to_xml.rb +5 -9
- data/lib/factbase/to_yaml.rb +1 -1
- data/lib/factbase/version.rb +1 -2
- data/lib/factbase.rb +27 -10
- data/lib/fuzz.rb +3 -3
- metadata +3 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Indexed term 'gte'.
|
|
7
|
+
class Factbase::IndexedGte
|
|
8
|
+
def initialize(term, idx)
|
|
9
|
+
@term = term
|
|
10
|
+
@idx = idx
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def predict(maps, _fb, params)
|
|
14
|
+
op1, op2 = @term.operands
|
|
15
|
+
return unless op1.is_a?(Symbol) && _scalar?(op2)
|
|
16
|
+
prop = op1.to_s
|
|
17
|
+
target = op2.is_a?(Symbol) ? params[op2.to_s]&.first : op2
|
|
18
|
+
return maps || [] if target.nil?
|
|
19
|
+
key = [maps.object_id, prop, :facts]
|
|
20
|
+
@idx[key] ||= { facts: [], count: 0 }
|
|
21
|
+
entry = @idx[key]
|
|
22
|
+
_feed(maps.to_a, entry, prop)
|
|
23
|
+
matched = _search(entry, target)
|
|
24
|
+
maps.respond_to?(:repack) ? maps.repack(matched) : matched
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def _scalar?(item)
|
|
30
|
+
item.is_a?(String) || item.is_a?(Time) || item.is_a?(Integer) || item.is_a?(Float) || item.is_a?(Symbol)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _feed(facts, entry, prop)
|
|
34
|
+
return unless entry[:count] < facts.size
|
|
35
|
+
facts[entry[:count]..].each do |fact|
|
|
36
|
+
fact[prop]&.each do |v|
|
|
37
|
+
entry[:facts] << [v, fact]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
entry[:facts].sort_by! { |pair| pair[0] }
|
|
41
|
+
entry[:count] = facts.size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _search(entry, target)
|
|
45
|
+
idx = entry[:facts].bsearch_index { |v, _| v >= target }
|
|
46
|
+
return [] if idx.nil?
|
|
47
|
+
facts = entry[:facts][idx..].map { |_, f| f }
|
|
48
|
+
facts.uniq!(&:object_id)
|
|
49
|
+
facts
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -44,6 +44,8 @@ class Factbase::IndexedLt
|
|
|
44
44
|
def _search(entry, target)
|
|
45
45
|
idx = entry[:facts].bsearch_index { |v, _| v >= target }
|
|
46
46
|
res = idx.nil? ? entry[:facts] : entry[:facts][0...idx]
|
|
47
|
-
res.map { |_, f| f }
|
|
47
|
+
facts = res.map { |_, f| f }
|
|
48
|
+
facts.uniq!(&:object_id)
|
|
49
|
+
facts
|
|
48
50
|
end
|
|
49
51
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Indexed term 'lte'.
|
|
7
|
+
class Factbase::IndexedLte
|
|
8
|
+
def initialize(term, idx)
|
|
9
|
+
@term = term
|
|
10
|
+
@idx = idx
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def predict(maps, _fb, params)
|
|
14
|
+
op1, op2 = @term.operands
|
|
15
|
+
return unless op1.is_a?(Symbol) && _scalar?(op2)
|
|
16
|
+
prop = op1.to_s
|
|
17
|
+
target = op2.is_a?(Symbol) ? params[op2.to_s]&.first : op2
|
|
18
|
+
return maps || [] if target.nil?
|
|
19
|
+
key = [maps.object_id, prop, :facts]
|
|
20
|
+
@idx[key] ||= { facts: [], count: 0 }
|
|
21
|
+
entry = @idx[key]
|
|
22
|
+
_feed(maps.to_a, entry, prop)
|
|
23
|
+
matched = _search(entry, target)
|
|
24
|
+
maps.respond_to?(:repack) ? maps.repack(matched) : matched
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def _scalar?(item)
|
|
30
|
+
item.is_a?(String) || item.is_a?(Time) || item.is_a?(Integer) || item.is_a?(Float) || item.is_a?(Symbol)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def _feed(facts, entry, prop)
|
|
34
|
+
return unless entry[:count] < facts.size
|
|
35
|
+
facts[entry[:count]..].each do |fact|
|
|
36
|
+
fact[prop]&.each do |v|
|
|
37
|
+
entry[:facts] << [v, fact]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
entry[:facts].sort_by! { |pair| pair[0] }
|
|
41
|
+
entry[:count] = facts.size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _search(entry, target)
|
|
45
|
+
idx = entry[:facts].bsearch_index { |v, _| v > target }
|
|
46
|
+
res = idx.nil? ? entry[:facts] : entry[:facts][0...idx]
|
|
47
|
+
facts = res.map { |_, f| f }
|
|
48
|
+
facts.uniq!(&:object_id)
|
|
49
|
+
facts
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -11,7 +11,7 @@ class Factbase::IndexedOr
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def predict(maps, fb, params)
|
|
14
|
-
return
|
|
14
|
+
return if @idx.nil?
|
|
15
15
|
r = nil
|
|
16
16
|
@term.operands.each do |o|
|
|
17
17
|
n = o.predict(maps, fb, params)
|
|
@@ -21,7 +21,7 @@ class Factbase::IndexedOr
|
|
|
21
21
|
end
|
|
22
22
|
r = maps & [] if r.nil?
|
|
23
23
|
r |= n.to_a
|
|
24
|
-
return maps if r.size > maps.size / 4
|
|
24
|
+
return maps if r.size > maps.size / 4
|
|
25
25
|
end
|
|
26
26
|
r
|
|
27
27
|
end
|
|
@@ -37,11 +37,12 @@ class Factbase::IndexedQuery
|
|
|
37
37
|
# @return [Integer] Total number of facts yielded
|
|
38
38
|
def each(fb = @fb, params = {})
|
|
39
39
|
return to_enum(__method__, fb, params) unless block_given?
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
yield
|
|
40
|
+
n = 0
|
|
41
|
+
@origin.each(fb, params) do |f|
|
|
42
|
+
yield(Factbase::IndexedFact.new(f, @idx, @fresh))
|
|
43
|
+
n += 1
|
|
43
44
|
end
|
|
44
|
-
|
|
45
|
+
n
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
# Read a single value.
|
|
@@ -56,8 +57,6 @@ class Factbase::IndexedQuery
|
|
|
56
57
|
# @param [Factbase] fb The factbase
|
|
57
58
|
# @return [Integer] Total number of facts deleted
|
|
58
59
|
def delete!(fb = @fb)
|
|
59
|
-
|
|
60
|
-
@idx.clear
|
|
61
|
-
result
|
|
60
|
+
@origin.delete!(fb).tap { @idx.clear }
|
|
62
61
|
end
|
|
63
62
|
end
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
require 'tago'
|
|
7
7
|
require_relative '../../factbase'
|
|
8
|
+
require_relative '../indexed/indexed_absent'
|
|
9
|
+
require_relative '../indexed/indexed_and'
|
|
8
10
|
require_relative '../indexed/indexed_eq'
|
|
9
|
-
require_relative '../indexed/
|
|
11
|
+
require_relative '../indexed/indexed_exists'
|
|
10
12
|
require_relative '../indexed/indexed_gt'
|
|
11
|
-
require_relative '../indexed/
|
|
13
|
+
require_relative '../indexed/indexed_gte'
|
|
14
|
+
require_relative '../indexed/indexed_lt'
|
|
15
|
+
require_relative '../indexed/indexed_lte'
|
|
12
16
|
require_relative '../indexed/indexed_not'
|
|
13
|
-
require_relative '../indexed/
|
|
14
|
-
require_relative '../indexed/indexed_and'
|
|
17
|
+
require_relative '../indexed/indexed_one'
|
|
15
18
|
require_relative '../indexed/indexed_or'
|
|
16
|
-
require_relative '../indexed/indexed_absent'
|
|
17
19
|
require_relative '../indexed/indexed_unique'
|
|
18
20
|
|
|
19
21
|
# Term with an index.
|
|
@@ -35,7 +37,7 @@ module Factbase::IndexedTerm
|
|
|
35
37
|
return t.predict(maps, fb, params) if t.respond_to?(:predict)
|
|
36
38
|
end
|
|
37
39
|
m = :"#{@op}_predict"
|
|
38
|
-
return
|
|
40
|
+
return __send__(m, maps, fb, params) if respond_to?(m)
|
|
39
41
|
_init_indexes unless @indexes
|
|
40
42
|
@indexes[@op].predict(maps, fb, params) if @indexes.key?(@op)
|
|
41
43
|
end
|
|
@@ -46,7 +48,9 @@ module Factbase::IndexedTerm
|
|
|
46
48
|
@indexes = {
|
|
47
49
|
eq: Factbase::IndexedEq.new(self, @idx),
|
|
48
50
|
lt: Factbase::IndexedLt.new(self, @idx),
|
|
51
|
+
lte: Factbase::IndexedLte.new(self, @idx),
|
|
49
52
|
gt: Factbase::IndexedGt.new(self, @idx),
|
|
53
|
+
gte: Factbase::IndexedGte.new(self, @idx),
|
|
50
54
|
one: Factbase::IndexedOne.new(self, @idx),
|
|
51
55
|
exists: Factbase::IndexedExists.new(self, @idx),
|
|
52
56
|
absent: Factbase::IndexedAbsent.new(self, @idx),
|
data/lib/factbase/inv.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'others'
|
|
7
6
|
require 'decoor'
|
|
7
|
+
require 'others'
|
|
8
8
|
require_relative '../factbase'
|
|
9
9
|
|
|
10
10
|
# A decorator of a Factbase, that checks invariants on every set.
|
|
@@ -40,7 +40,7 @@ class Factbase::Inv
|
|
|
40
40
|
|
|
41
41
|
def txn
|
|
42
42
|
@fb.txn do |fbt|
|
|
43
|
-
yield
|
|
43
|
+
yield(Factbase::Inv.new(fbt, &@block))
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -89,7 +89,7 @@ class Factbase::Inv
|
|
|
89
89
|
def each(fb = @fb, params = {})
|
|
90
90
|
return to_enum(__method__, fb, params) unless block_given?
|
|
91
91
|
@query.each(fb, params) do |f|
|
|
92
|
-
yield
|
|
92
|
+
yield(Fact.new(f, @block))
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
end
|
data/lib/factbase/lazy_taped.rb
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
require_relative '../factbase'
|
|
7
|
-
require_relative 'taped'
|
|
8
7
|
require_relative 'lazy_taped_hash'
|
|
8
|
+
require_relative 'taped'
|
|
9
9
|
|
|
10
10
|
# A lazy decorator of an Array with HashMaps that defers copying until modification.
|
|
11
11
|
class Factbase::LazyTaped
|
|
@@ -22,7 +22,7 @@ class Factbase::LazyTaped
|
|
|
22
22
|
# Returns the original map this copy was derived from.
|
|
23
23
|
# Returns nil if the base hasn't been copied yet or if the fact is new.
|
|
24
24
|
def source_of(copy)
|
|
25
|
-
return
|
|
25
|
+
return unless @copied
|
|
26
26
|
@copies.key(copy)
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -76,21 +76,21 @@ class Factbase::LazyTaped
|
|
|
76
76
|
is_copied = copied?
|
|
77
77
|
unless is_copied
|
|
78
78
|
@origin.each do |m|
|
|
79
|
-
yield
|
|
79
|
+
yield(_tape(m))
|
|
80
80
|
yielded_size += 1
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
staged = is_copied == copied? ? @staged : @staged[yielded_size..]
|
|
84
84
|
staged&.each do |f|
|
|
85
85
|
next if f.nil?
|
|
86
|
-
yield
|
|
86
|
+
yield(_tape(f))
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def delete_if
|
|
91
91
|
ensure_copied!
|
|
92
92
|
@staged.delete_if do |m|
|
|
93
|
-
r = yield
|
|
93
|
+
r = yield(m)
|
|
94
94
|
@deleted.append(source_of(m).object_id) if r
|
|
95
95
|
r
|
|
96
96
|
end
|
|
@@ -102,8 +102,7 @@ class Factbase::LazyTaped
|
|
|
102
102
|
|
|
103
103
|
def repack(other)
|
|
104
104
|
ensure_copied!
|
|
105
|
-
|
|
106
|
-
Factbase::Taped.new(copied, inserted: @inserted, deleted: @deleted, added: @added)
|
|
105
|
+
Factbase::Taped.new(other.map { |o| @copies[o] || o }, inserted: @inserted, deleted: @deleted, added: @added)
|
|
107
106
|
end
|
|
108
107
|
|
|
109
108
|
def &(other)
|
|
@@ -135,10 +134,9 @@ class Factbase::LazyTaped
|
|
|
135
134
|
|
|
136
135
|
def _join(other)
|
|
137
136
|
ensure_copied!
|
|
138
|
-
|
|
139
|
-
raise '
|
|
140
|
-
|
|
141
|
-
Factbase::Taped.new(n, inserted: @inserted, deleted: @deleted, added: @added)
|
|
137
|
+
raise(ArgumentError, 'Cannot join with another Taped') if other.respond_to?(:inserted)
|
|
138
|
+
raise(ArgumentError, 'Can only join with array') unless other.is_a?(Array)
|
|
139
|
+
Factbase::Taped.new(yield(to_a, other.to_a), inserted: @inserted, deleted: @deleted, added: @added)
|
|
142
140
|
end
|
|
143
141
|
|
|
144
142
|
def _track(copy, original)
|
|
@@ -147,7 +145,6 @@ class Factbase::LazyTaped
|
|
|
147
145
|
|
|
148
146
|
def _tape(map)
|
|
149
147
|
return LazyTapedHash.new(map, self, @added) unless copied?
|
|
150
|
-
|
|
151
|
-
Factbase::Taped::TapedHash.new(copy, @added)
|
|
148
|
+
Factbase::Taped::TapedHash.new(@copies[map] || map, @added)
|
|
152
149
|
end
|
|
153
150
|
end
|
|
@@ -65,7 +65,8 @@ class Factbase::LazyTaped
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def method_missing(method, *, &)
|
|
68
|
-
|
|
68
|
+
ensure_copied_map if method.to_s.end_with?('=', '!')
|
|
69
|
+
current_map.__send__(method, *, &)
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def respond_to_missing?(method, include_private = false)
|
data/lib/factbase/light.rb
CHANGED
data/lib/factbase/logged.rb
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
require 'decoor'
|
|
7
7
|
require 'others'
|
|
8
|
-
require 'time'
|
|
9
8
|
require 'tago'
|
|
9
|
+
require 'time'
|
|
10
10
|
require_relative 'syntax'
|
|
11
11
|
|
|
12
12
|
# A decorator of a Factbase, that logs all operations.
|
|
@@ -15,16 +15,18 @@ require_relative 'syntax'
|
|
|
15
15
|
# Copyright:: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
16
16
|
# License:: MIT
|
|
17
17
|
class Factbase::Logged
|
|
18
|
+
MONO = Process::CLOCK_MONOTONIC
|
|
19
|
+
|
|
18
20
|
# Ctor.
|
|
19
21
|
# @param [Factbase] fb The factbase to decorate
|
|
20
22
|
# @param [Object] log The logging facility
|
|
21
23
|
# @param [Integer] time_tolerate How many seconds are OK per request
|
|
22
24
|
# @param [Print] tube The tube to use, if log is NIL
|
|
23
25
|
def initialize(fb, log = nil, time_tolerate: 1, tube: nil)
|
|
24
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
26
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
25
27
|
@origin = fb
|
|
26
28
|
if log.nil?
|
|
27
|
-
raise 'Either "log" or "tube" must be non-NIL' if tube.nil?
|
|
29
|
+
raise(ArgumentError, 'Either "log" or "tube" must be non-NIL') if tube.nil?
|
|
28
30
|
@tube = tube
|
|
29
31
|
else
|
|
30
32
|
@tube = Tube.new(log, time_tolerate:)
|
|
@@ -34,10 +36,8 @@ class Factbase::Logged
|
|
|
34
36
|
decoor(:origin)
|
|
35
37
|
|
|
36
38
|
def insert
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@tube.say(start, "Inserted new fact ##{@origin.size} in #{start.ago}")
|
|
40
|
-
Fact.new(f, tube: @tube)
|
|
39
|
+
@tube.say(Process.clock_gettime(MONO), "Inserted new fact ##{@origin.size} in #{Time.now.ago}")
|
|
40
|
+
Fact.new(@origin.insert, tube: @tube)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def query(term, maps = nil)
|
|
@@ -46,21 +46,21 @@ class Factbase::Logged
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def txn
|
|
49
|
-
|
|
49
|
+
mono = Process.clock_gettime(MONO)
|
|
50
50
|
id = nil
|
|
51
51
|
rollback = false
|
|
52
52
|
r =
|
|
53
53
|
@origin.txn do |fbt|
|
|
54
54
|
id = fbt.object_id
|
|
55
|
-
yield
|
|
55
|
+
yield(Factbase::Logged.new(fbt, tube: @tube))
|
|
56
56
|
rescue Factbase::Rollback => e
|
|
57
57
|
rollback = true
|
|
58
|
-
raise
|
|
58
|
+
raise(e)
|
|
59
59
|
end
|
|
60
60
|
if rollback
|
|
61
|
-
@tube.say(
|
|
61
|
+
@tube.say(mono, "Txn ##{id} rolled back in #{Time.now.ago}")
|
|
62
62
|
else
|
|
63
|
-
@tube.say(
|
|
63
|
+
@tube.say(mono, "Txn ##{id} touched #{r} in #{Time.now.ago}")
|
|
64
64
|
end
|
|
65
65
|
r
|
|
66
66
|
end
|
|
@@ -72,13 +72,13 @@ class Factbase::Logged
|
|
|
72
72
|
@time_tolerate = time_tolerate
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def say(
|
|
75
|
+
def say(start_mono, msg)
|
|
76
76
|
m = :debug
|
|
77
|
-
if
|
|
77
|
+
if Process.clock_gettime(Factbase::Logged::MONO) - start_mono > @time_tolerate
|
|
78
78
|
msg = "#{msg} (slow!)"
|
|
79
79
|
m = :warn
|
|
80
80
|
end
|
|
81
|
-
@log.
|
|
81
|
+
@log.__send__(m, msg)
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
@@ -108,14 +108,16 @@ class Factbase::Logged
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
others do |*args|
|
|
111
|
-
|
|
111
|
+
mono = Process.clock_gettime(Factbase::Logged::MONO) if args[0].to_s.end_with?('=')
|
|
112
112
|
r = @fact.method_missing(*args)
|
|
113
113
|
k = args[0].to_s
|
|
114
114
|
v = args[1]
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
if k.end_with?('=')
|
|
116
|
+
s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
|
|
117
|
+
s = v.to_s.inspect if v.is_a?(String)
|
|
118
|
+
s = "#{s[0..(MAX_LENGTH / 2)]}...#{s[(-MAX_LENGTH / 2)..]}" if s.length > MAX_LENGTH
|
|
119
|
+
@tube.say(mono, "Set '#{k[0..-2]}' to #{s} (#{v.class})")
|
|
120
|
+
end
|
|
119
121
|
r
|
|
120
122
|
end
|
|
121
123
|
end
|
|
@@ -137,26 +139,28 @@ class Factbase::Logged
|
|
|
137
139
|
|
|
138
140
|
def each(fb = @fb, params = {}, &)
|
|
139
141
|
return to_enum(__method__, fb, params) unless block_given?
|
|
140
|
-
|
|
142
|
+
mono = Process.clock_gettime(Factbase::Logged::MONO)
|
|
141
143
|
r = nil
|
|
142
144
|
qry = @fb.query(@term, @maps)
|
|
143
145
|
tail =
|
|
144
146
|
Factbase::Logged.elapsed do
|
|
145
147
|
r = qry.each(fb, params, &)
|
|
146
148
|
end
|
|
147
|
-
|
|
149
|
+
unless r.is_a?(Integer)
|
|
150
|
+
raise(StandardError, ".query(#{@term.to_s.inspect}).each() of #{qry.class} returned #{r.class}")
|
|
151
|
+
end
|
|
148
152
|
q = Factbase::Syntax.new(@term).to_term.to_s
|
|
149
153
|
q = "#{q} with {#{params.map { |k, v| "#{k}=#{v}" }.join(', ')}}" if params.is_a?(Hash) && !params.empty?
|
|
150
154
|
if r.zero?
|
|
151
|
-
@tube.say(
|
|
155
|
+
@tube.say(mono, "Zero/#{@fb.size} facts found by #{q} #{tail}")
|
|
152
156
|
else
|
|
153
|
-
@tube.say(
|
|
157
|
+
@tube.say(mono, "Found #{r}/#{@fb.size} fact(s) by #{q} #{tail}")
|
|
154
158
|
end
|
|
155
159
|
r
|
|
156
160
|
end
|
|
157
161
|
|
|
158
162
|
def one(fb = @fb, params = {})
|
|
159
|
-
|
|
163
|
+
mono = Process.clock_gettime(Factbase::Logged::MONO)
|
|
160
164
|
q = Factbase::Syntax.new(@term).to_term.to_s
|
|
161
165
|
r = nil
|
|
162
166
|
tail =
|
|
@@ -164,36 +168,35 @@ class Factbase::Logged
|
|
|
164
168
|
r = @fb.query(@term, @maps).one(fb, params)
|
|
165
169
|
end
|
|
166
170
|
if r.nil?
|
|
167
|
-
@tube.say(
|
|
171
|
+
@tube.say(mono, "Nothing found by '#{q}' #{tail}")
|
|
168
172
|
else
|
|
169
|
-
@tube.say(
|
|
173
|
+
@tube.say(mono, "Found #{r} (#{r.class}) by '#{q}' #{tail}")
|
|
170
174
|
end
|
|
171
175
|
r
|
|
172
176
|
end
|
|
173
177
|
|
|
174
178
|
def delete!(fb = @fb)
|
|
175
179
|
r = nil
|
|
176
|
-
|
|
180
|
+
mono = Process.clock_gettime(Factbase::Logged::MONO)
|
|
177
181
|
before = @fb.size
|
|
178
182
|
tail =
|
|
179
183
|
Factbase::Logged.elapsed do
|
|
180
184
|
r = @fb.query(@term, @maps).delete!(fb)
|
|
181
185
|
end
|
|
182
|
-
raise ".delete! of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
|
|
186
|
+
raise(StandardError, ".delete! of #{@term.class} returned #{r.class}") unless r.is_a?(Integer)
|
|
183
187
|
if before.zero?
|
|
184
|
-
@tube.say(
|
|
188
|
+
@tube.say(mono, "There were no facts, nothing deleted by #{@term} #{tail}")
|
|
185
189
|
elsif r.zero?
|
|
186
|
-
@tube.say(
|
|
190
|
+
@tube.say(mono, "No facts out of #{before} deleted by #{@term} #{tail}")
|
|
187
191
|
else
|
|
188
|
-
@tube.say(
|
|
192
|
+
@tube.say(mono, "Deleted #{r} fact(s) out of #{before} by #{@term} #{tail}")
|
|
189
193
|
end
|
|
190
194
|
r
|
|
191
195
|
end
|
|
192
196
|
end
|
|
193
197
|
|
|
194
198
|
def self.elapsed
|
|
195
|
-
start = Time.now
|
|
196
199
|
yield
|
|
197
|
-
"in #{
|
|
200
|
+
"in #{Time.now.ago}"
|
|
198
201
|
end
|
|
199
202
|
end
|
data/lib/factbase/pre.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'loog'
|
|
7
6
|
require 'decoor'
|
|
7
|
+
require 'loog'
|
|
8
8
|
require_relative '../factbase'
|
|
9
9
|
|
|
10
10
|
# A decorator of a +Factbase+, that runs a provided block on every +insert+.
|
|
@@ -26,7 +26,7 @@ class Factbase::Pre
|
|
|
26
26
|
decoor(:fb)
|
|
27
27
|
|
|
28
28
|
def initialize(fb, &block)
|
|
29
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
29
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
30
30
|
@fb = fb
|
|
31
31
|
@block = block
|
|
32
32
|
end
|
|
@@ -39,7 +39,7 @@ class Factbase::Pre
|
|
|
39
39
|
|
|
40
40
|
def txn
|
|
41
41
|
@fb.txn do |fbt|
|
|
42
|
-
yield
|
|
42
|
+
yield(Factbase::Pre.new(fbt, &@block))
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
data/lib/factbase/query.rb
CHANGED
|
@@ -52,13 +52,12 @@ class Factbase::Query
|
|
|
52
52
|
extras = {}
|
|
53
53
|
f = Factbase::Fact.new(m)
|
|
54
54
|
f = Factbase::Tee.new(f, params)
|
|
55
|
-
|
|
56
|
-
r = @term.evaluate(a, @maps, fb)
|
|
55
|
+
r = @term.evaluate(Factbase::Accum.new(f, extras, false), @maps, fb)
|
|
57
56
|
unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
|
58
|
-
raise "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@term.inspect}"
|
|
57
|
+
raise(ArgumentError, "Unexpected evaluation result of type #{r.class}, must be Boolean at #{@term.inspect}")
|
|
59
58
|
end
|
|
60
59
|
next unless r
|
|
61
|
-
yield
|
|
60
|
+
yield(Factbase::Accum.new(f, extras, true))
|
|
62
61
|
yielded += 1
|
|
63
62
|
end
|
|
64
63
|
yielded
|
|
@@ -72,7 +71,7 @@ class Factbase::Query
|
|
|
72
71
|
params = params.transform_keys(&:to_s) if params.is_a?(Hash)
|
|
73
72
|
r = @term.evaluate(Factbase::Tee.new(Factbase::Fact.new({}), params), @maps, fb)
|
|
74
73
|
unless %w[String Integer Float Time Array NilClass].include?(r.class.to_s)
|
|
75
|
-
raise "Incorrect type #{r.class} returned by #{@term.inspect}"
|
|
74
|
+
raise(StandardError, "Incorrect type #{r.class} returned by #{@term.inspect}")
|
|
76
75
|
end
|
|
77
76
|
r
|
|
78
77
|
end
|
data/lib/factbase/rules.rb
CHANGED
|
@@ -28,11 +28,11 @@ class Factbase::Rules
|
|
|
28
28
|
decoor(:fb)
|
|
29
29
|
|
|
30
30
|
def initialize(fb, rules, check = Check.new(rules), uid: nil)
|
|
31
|
-
raise 'The "fb" is nil' if fb.nil?
|
|
31
|
+
raise(ArgumentError, 'The "fb" is nil') if fb.nil?
|
|
32
32
|
@fb = fb
|
|
33
|
-
raise 'The "rules" is nil' if rules.nil?
|
|
33
|
+
raise(ArgumentError, 'The "rules" is nil') if rules.nil?
|
|
34
34
|
@rules = rules
|
|
35
|
-
raise 'The "check" is nil' if check.nil?
|
|
35
|
+
raise(ArgumentError, 'The "check" is nil') if check.nil?
|
|
36
36
|
@check = check
|
|
37
37
|
@uid = uid
|
|
38
38
|
end
|
|
@@ -51,7 +51,7 @@ class Factbase::Rules
|
|
|
51
51
|
@check = later
|
|
52
52
|
@fb.txn do |fbt|
|
|
53
53
|
churn = Factbase::Churn.new
|
|
54
|
-
yield
|
|
54
|
+
yield(Factbase::Tallied.new(Factbase::Rules.new(fbt, @rules, @check, uid: @uid), churn))
|
|
55
55
|
@check = before
|
|
56
56
|
unless churn.zero?
|
|
57
57
|
fbt.query('(always)').each do |f|
|
|
@@ -107,7 +107,7 @@ class Factbase::Rules
|
|
|
107
107
|
def each(fb = @fb, params = {})
|
|
108
108
|
return to_enum(__method__, fb, params) unless block_given?
|
|
109
109
|
@query.each(fb, params) do |f|
|
|
110
|
-
yield
|
|
110
|
+
yield(Fact.new(f, @check, fb))
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
end
|
|
@@ -123,7 +123,7 @@ class Factbase::Rules
|
|
|
123
123
|
def it(fact, fb)
|
|
124
124
|
return if Factbase::Syntax.new(@expr).to_term.evaluate(fact, [], fb)
|
|
125
125
|
e = "#{@expr[0..32]}..." if @expr.length > 32
|
|
126
|
-
raise "The fact doesn't match the #{e.inspect} rule: #{fact}"
|
|
126
|
+
raise(ArgumentError, "The fact doesn't match the #{e.inspect} rule: #{fact}")
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
@@ -140,7 +140,7 @@ class Factbase::Rules
|
|
|
140
140
|
return if @uid.nil?
|
|
141
141
|
a = fact[@uid]
|
|
142
142
|
return if a.nil?
|
|
143
|
-
raise "More than one #{@uid.inspect} in the fact: #{a}" if a.size > 1
|
|
143
|
+
raise(ArgumentError, "More than one #{@uid.inspect} in the fact: #{a}") if a.size > 1
|
|
144
144
|
@facts << a.first
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -148,7 +148,7 @@ class Factbase::Rules
|
|
|
148
148
|
return true if @uid.nil?
|
|
149
149
|
a = fact[@uid]
|
|
150
150
|
return true if a.nil?
|
|
151
|
-
raise "More than one #{@uid.inspect} in the fact: #{a}" if a.size > 1
|
|
151
|
+
raise(ArgumentError, "More than one #{@uid.inspect} in the fact: #{a}") if a.size > 1
|
|
152
152
|
@facts.include?(a.first)
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -43,7 +43,7 @@ class Factbase::SyncFactbase
|
|
|
43
43
|
# @param [Array<Hash>] maps Possible maps to use
|
|
44
44
|
def query(term, maps = nil)
|
|
45
45
|
term = to_term(term) if term.is_a?(String)
|
|
46
|
-
require_relative
|
|
46
|
+
require_relative('sync_query')
|
|
47
47
|
Factbase::SyncQuery.new(@origin.query(term, maps), @monitor, self)
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -53,7 +53,7 @@ class Factbase::SyncFactbase
|
|
|
53
53
|
def txn
|
|
54
54
|
try_lock do
|
|
55
55
|
@origin.txn do |fbt|
|
|
56
|
-
yield
|
|
56
|
+
yield(Factbase::SyncFactbase.new(fbt, @monitor))
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|