factbase 0.18.0 → 0.19.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/README.md +45 -27
- data/lib/factbase/cached/cached_fact.rb +7 -3
- data/lib/factbase/cached/cached_factbase.rb +1 -2
- data/lib/factbase/indexed/indexed_absent.rb +11 -5
- data/lib/factbase/indexed/indexed_and.rb +18 -7
- data/lib/factbase/indexed/indexed_eq.rb +15 -9
- data/lib/factbase/indexed/indexed_exists.rb +11 -5
- data/lib/factbase/indexed/indexed_fact.rb +7 -3
- data/lib/factbase/indexed/indexed_factbase.rb +2 -4
- data/lib/factbase/indexed/indexed_gt.rb +13 -7
- data/lib/factbase/indexed/indexed_lt.rb +18 -7
- data/lib/factbase/indexed/indexed_not.rb +14 -5
- data/lib/factbase/indexed/indexed_one.rb +11 -5
- data/lib/factbase/indexed/indexed_query.rb +3 -2
- data/lib/factbase/indexed/indexed_unique.rb +12 -3
- data/lib/factbase/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7bf5fda6f7bf7cfd9e62897b505924688938a0acfb326e0533ad80e9be8e01cd
|
|
4
|
+
data.tar.gz: 10f2a249d9e3eb5bcc560a6cf07606cec54a6d35d325c00c7c6ee17075cc92f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bef44475330c1835b4822894ca5460427e47271b60927d7dc920889dc77d5eb2249f77f334a9c283d87df137342642c10042ea66b41ad2de71ddfeb4a17443f9
|
|
7
|
+
data.tar.gz: 0b5d3cbfff4ac2479ea5887fae0e6b545dfe5b1dc5e008503f04a517eb6922a1604864d6348864515a87c2518ab3c227a62fa34a35659cffee73c97be7209dbd
|
data/Gemfile.lock
CHANGED
|
@@ -36,7 +36,7 @@ GEM
|
|
|
36
36
|
logger (1.7.0)
|
|
37
37
|
loog (0.6.1)
|
|
38
38
|
logger (~> 1.0)
|
|
39
|
-
minitest (5.
|
|
39
|
+
minitest (5.27.0)
|
|
40
40
|
minitest-reporters (1.7.1)
|
|
41
41
|
ansi
|
|
42
42
|
builder
|
|
@@ -126,6 +126,7 @@ PLATFORMS
|
|
|
126
126
|
arm64-darwin-22
|
|
127
127
|
arm64-darwin-23
|
|
128
128
|
arm64-darwin-24
|
|
129
|
+
arm64-darwin-25
|
|
129
130
|
x64-mingw-ucrt
|
|
130
131
|
x86_64-darwin-20
|
|
131
132
|
x86_64-darwin-21
|
data/README.md
CHANGED
|
@@ -210,36 +210,54 @@ This is the result of the benchmark:
|
|
|
210
210
|
|
|
211
211
|
<!-- benchmark_begin -->
|
|
212
212
|
```text
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
query 10 times w/
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(and (
|
|
231
|
-
(eq
|
|
232
|
-
(
|
|
233
|
-
(and (eq what
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
213
|
+
|
|
214
|
+
query all facts from an empty factbase 0.00
|
|
215
|
+
insert 20000 facts 0.67
|
|
216
|
+
export 20000 facts 0.02
|
|
217
|
+
import 411032 bytes (20000 facts) 0.03
|
|
218
|
+
insert 10 facts 0.04
|
|
219
|
+
query 10 times w/txn 2.52
|
|
220
|
+
query 10 times w/o txn 0.05
|
|
221
|
+
modify 10 attrs w/txn 1.84
|
|
222
|
+
delete 10 facts w/txn 2.98
|
|
223
|
+
build index on 5000 facts 0.05
|
|
224
|
+
export 5000 facts with index 0.03
|
|
225
|
+
import 5000 facts with persisted index 0.05
|
|
226
|
+
query 5000 facts using persisted index 0.11
|
|
227
|
+
export 5000 facts without index 0.00
|
|
228
|
+
import 5000 facts without index 0.04
|
|
229
|
+
query 5000 facts building index on-the-fly 0.10
|
|
230
|
+
(and (eq what 'issue-was-closed') (exists... -> 200 1.21
|
|
231
|
+
(and (eq what 'issue-was-closed') (exists... -> 200/txn 1.20
|
|
232
|
+
(and (eq what 'issue-was-closed') (exists... -> zero 1.21
|
|
233
|
+
(and (eq what 'issue-was-closed') (exists... -> zero/txn 1.23
|
|
234
|
+
transaction rollback on factbase with 100000 facts 0.25
|
|
235
|
+
(gt time '2024-03-23T03:21:43Z') 0.33
|
|
236
|
+
(gt cost 50) 0.21
|
|
237
|
+
(eq title 'Object Thinking 5000') 0.05
|
|
238
|
+
(and (eq foo 42.998) (or (gt bar 200) (absent z... 0.05
|
|
239
|
+
(and (exists foo) (not (exists blue))) 1.68
|
|
240
|
+
(eq id (agg (always) (max id))) 2.76
|
|
241
|
+
(join "c<=cost,b<=bar" (eq id (agg (always) (ma... 4.02
|
|
242
|
+
(and (eq what "foo") (join "w<=what" (and (eq i... 7.68
|
|
243
|
+
delete! 0.56
|
|
244
|
+
(and (eq issue *) (eq repository *) (eq what '*') (eq where '*')) 2.75
|
|
245
|
+
Taped.append() x50000 0.16
|
|
246
|
+
Taped.each() x125 1.63
|
|
247
|
+
Taped.delete_if() x375 0.84
|
|
248
|
+
50000 facts: read-only txn (no copy needed) 7.60
|
|
249
|
+
50000 facts: rollback txn (no copy needed) 7.47
|
|
250
|
+
50000 facts: insert in txn (copy triggered) 3.64
|
|
251
|
+
50000 facts: modify in txn (copy triggered) 39.42
|
|
252
|
+
100000 facts: read-only txn (no copy needed) 15.23
|
|
253
|
+
100000 facts: rollback txn (no copy needed) 15.17
|
|
254
|
+
100000 facts: insert in txn (copy triggered) 7.45
|
|
255
|
+
100000 facts: modify in txn (copy triggered) 73.97
|
|
238
256
|
```
|
|
239
257
|
|
|
240
258
|
The results were calculated in [this GHA job][benchmark-gha]
|
|
241
|
-
on 2025-
|
|
259
|
+
on 2025-12-11 at 13:29,
|
|
242
260
|
on Linux with 4 CPUs.
|
|
243
261
|
<!-- benchmark_end -->
|
|
244
262
|
|
|
245
|
-
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/
|
|
263
|
+
[benchmark-gha]: https://github.com/yegor256/factbase/actions/runs/20134646978
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
require 'others'
|
|
7
7
|
require_relative '../../factbase'
|
|
8
8
|
|
|
9
|
-
# A single fact in a factbase, which is
|
|
9
|
+
# A single fact in a factbase, which is sensitive to changes.
|
|
10
10
|
#
|
|
11
11
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
12
12
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
@@ -15,9 +15,11 @@ class Factbase::CachedFact
|
|
|
15
15
|
# Ctor.
|
|
16
16
|
# @param [Factbase::Fact] origin The original fact
|
|
17
17
|
# @param [Hash] cache Cache of queries (to clean it on attribute addition)
|
|
18
|
-
|
|
18
|
+
# @param [Boolean] fresh True if this is a newly inserted fact (not yet in cache)
|
|
19
|
+
def initialize(origin, cache, fresh: false)
|
|
19
20
|
@origin = origin
|
|
20
21
|
@cache = cache
|
|
22
|
+
@fresh = fresh
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def to_s
|
|
@@ -26,7 +28,9 @@ class Factbase::CachedFact
|
|
|
26
28
|
|
|
27
29
|
# When a method is missing, this method is called.
|
|
28
30
|
others do |*args|
|
|
29
|
-
|
|
31
|
+
# Only clear cache when modifying properties on existing (non-fresh) facts
|
|
32
|
+
# Fresh facts are not in the cache yet, so modifications don't affect it
|
|
33
|
+
@cache.clear if args[0].to_s.end_with?('=') && !@fresh
|
|
30
34
|
@origin.send(*args)
|
|
31
35
|
end
|
|
32
36
|
end
|
|
@@ -31,8 +31,7 @@ class Factbase::CachedFactbase
|
|
|
31
31
|
# Insert a new fact and return it.
|
|
32
32
|
# @return [Factbase::Fact] The fact just inserted
|
|
33
33
|
def insert
|
|
34
|
-
@cache
|
|
35
|
-
Factbase::CachedFact.new(@origin.insert, @cache)
|
|
34
|
+
Factbase::CachedFact.new(@origin.insert, @cache, fresh: true)
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
# Convert a query to a term.
|
|
@@ -13,13 +13,19 @@ class Factbase::IndexedAbsent
|
|
|
13
13
|
def predict(maps, _fb, _params)
|
|
14
14
|
return nil if @idx.nil?
|
|
15
15
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
entry = @idx[key]
|
|
17
|
+
maps_array = maps.to_a
|
|
18
|
+
if entry.nil?
|
|
19
|
+
entry = { facts: [], indexed_count: 0 }
|
|
20
|
+
@idx[key] = entry
|
|
21
|
+
end
|
|
22
|
+
if entry[:indexed_count] < maps_array.size
|
|
18
23
|
prop = @term.operands.first.to_s
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
25
|
+
entry[:facts] << m if m[prop].nil?
|
|
21
26
|
end
|
|
27
|
+
entry[:indexed_count] = maps_array.size
|
|
22
28
|
end
|
|
23
|
-
(maps & []) |
|
|
29
|
+
(maps & []) | entry[:facts]
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -18,14 +18,20 @@ class Factbase::IndexedAnd
|
|
|
18
18
|
&& @term.operands.all? { |o| o.operands.first.is_a?(Symbol) && _scalar?(o.operands[1]) }
|
|
19
19
|
props = @term.operands.map { |o| o.operands.first }.sort
|
|
20
20
|
key = [maps.object_id, props, :multi_and_eq]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
entry = @idx[key]
|
|
22
|
+
maps_array = maps.to_a
|
|
23
|
+
if entry.nil?
|
|
24
|
+
entry = { index: {}, indexed_count: 0 }
|
|
25
|
+
@idx[key] = entry
|
|
26
|
+
end
|
|
27
|
+
if entry[:indexed_count] < maps_array.size
|
|
28
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
24
29
|
_all_tuples(m, props).each do |t|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
entry[:index][t] ||= []
|
|
31
|
+
entry[:index][t] << m
|
|
27
32
|
end
|
|
28
33
|
end
|
|
34
|
+
entry[:indexed_count] = maps_array.size
|
|
29
35
|
end
|
|
30
36
|
tuples = Enumerator.product(
|
|
31
37
|
*@term.operands.sort_by { |o| o.operands.first }.map do |o|
|
|
@@ -36,8 +42,13 @@ class Factbase::IndexedAnd
|
|
|
36
42
|
end
|
|
37
43
|
end
|
|
38
44
|
)
|
|
39
|
-
j = tuples.
|
|
40
|
-
r =
|
|
45
|
+
j = tuples.flat_map { |t| entry[:index][t] || [] }.uniq(&:object_id)
|
|
46
|
+
r =
|
|
47
|
+
if maps.respond_to?(:inserted)
|
|
48
|
+
Factbase::Taped.new(j, inserted: maps.inserted, deleted: maps.deleted, added: maps.added)
|
|
49
|
+
else
|
|
50
|
+
j
|
|
51
|
+
end
|
|
41
52
|
else
|
|
42
53
|
@term.operands.each do |o|
|
|
43
54
|
n = o.predict(maps, fb, params)
|
|
@@ -14,15 +14,21 @@ class Factbase::IndexedEq
|
|
|
14
14
|
return nil if @idx.nil?
|
|
15
15
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
16
16
|
return unless @term.operands.first.is_a?(Symbol) && _scalar?(@term.operands[1])
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
entry = @idx[key]
|
|
18
|
+
maps_array = maps.to_a
|
|
19
|
+
if entry.nil?
|
|
20
|
+
entry = { index: {}, indexed_count: 0 }
|
|
21
|
+
@idx[key] = entry
|
|
22
|
+
end
|
|
23
|
+
if entry[:indexed_count] < maps_array.size
|
|
19
24
|
prop = @term.operands.first.to_s
|
|
20
|
-
|
|
25
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
21
26
|
m[prop]&.each do |v|
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
entry[:index][v] ||= []
|
|
28
|
+
entry[:index][v] << m
|
|
24
29
|
end
|
|
25
30
|
end
|
|
31
|
+
entry[:indexed_count] = maps_array.size
|
|
26
32
|
end
|
|
27
33
|
vv =
|
|
28
34
|
if @term.operands[1].is_a?(Symbol)
|
|
@@ -30,11 +36,11 @@ class Factbase::IndexedEq
|
|
|
30
36
|
else
|
|
31
37
|
[@term.operands[1]]
|
|
32
38
|
end
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
j = vv.flat_map { |v| entry[:index][v] || [] }.uniq(&:object_id)
|
|
40
|
+
if maps.respond_to?(:inserted)
|
|
41
|
+
Factbase::Taped.new(j, inserted: maps.inserted, deleted: maps.deleted, added: maps.added)
|
|
35
42
|
else
|
|
36
|
-
j
|
|
37
|
-
(maps & []) | j
|
|
43
|
+
j
|
|
38
44
|
end
|
|
39
45
|
end
|
|
40
46
|
|
|
@@ -13,13 +13,19 @@ class Factbase::IndexedExists
|
|
|
13
13
|
def predict(maps, _fb, _params)
|
|
14
14
|
return nil if @idx.nil?
|
|
15
15
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
entry = @idx[key]
|
|
17
|
+
maps_array = maps.to_a
|
|
18
|
+
if entry.nil?
|
|
19
|
+
entry = { facts: [], indexed_count: 0 }
|
|
20
|
+
@idx[key] = entry
|
|
21
|
+
end
|
|
22
|
+
if entry[:indexed_count] < maps_array.size
|
|
18
23
|
prop = @term.operands.first.to_s
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
25
|
+
entry[:facts] << m unless m[prop].nil?
|
|
21
26
|
end
|
|
27
|
+
entry[:indexed_count] = maps_array.size
|
|
22
28
|
end
|
|
23
|
-
(maps & []) |
|
|
29
|
+
(maps & []) | entry[:facts]
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
require 'others'
|
|
7
7
|
require_relative '../../factbase'
|
|
8
8
|
|
|
9
|
-
# A single fact in a factbase, which is
|
|
9
|
+
# A single fact in a factbase, which is sensitive to changes.
|
|
10
10
|
#
|
|
11
11
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
12
12
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
@@ -15,9 +15,11 @@ class Factbase::IndexedFact
|
|
|
15
15
|
# Ctor.
|
|
16
16
|
# @param [Factbase::Fact] origin The original fact
|
|
17
17
|
# @param [Hash] idx The index
|
|
18
|
-
|
|
18
|
+
# @param [Boolean] fresh True if this is a newly inserted fact (not yet in index)
|
|
19
|
+
def initialize(origin, idx, fresh: false)
|
|
19
20
|
@origin = origin
|
|
20
21
|
@idx = idx
|
|
22
|
+
@fresh = fresh
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def to_s
|
|
@@ -26,7 +28,9 @@ class Factbase::IndexedFact
|
|
|
26
28
|
|
|
27
29
|
# When a method is missing, this method is called.
|
|
28
30
|
others do |*args|
|
|
29
|
-
|
|
31
|
+
# Only clear index when modifying properties on existing (non-fresh) facts
|
|
32
|
+
# Fresh facts are not in the index yet, so modifications don't affect it
|
|
33
|
+
@idx.clear if args[0].to_s.end_with?('=') && !@fresh
|
|
30
34
|
@origin.send(*args)
|
|
31
35
|
end
|
|
32
36
|
end
|
|
@@ -31,8 +31,7 @@ class Factbase::IndexedFactbase
|
|
|
31
31
|
# Insert a new fact and return it.
|
|
32
32
|
# @return [Factbase::Fact] The fact just inserted
|
|
33
33
|
def insert
|
|
34
|
-
@idx
|
|
35
|
-
Factbase::IndexedFact.new(@origin.insert, @idx)
|
|
34
|
+
Factbase::IndexedFact.new(@origin.insert, @idx, fresh: true)
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
# Convert a query to a term.
|
|
@@ -50,8 +49,7 @@ class Factbase::IndexedFactbase
|
|
|
50
49
|
def query(term, maps = nil)
|
|
51
50
|
term = to_term(term) if term.is_a?(String)
|
|
52
51
|
q = @origin.query(term, maps)
|
|
53
|
-
|
|
54
|
-
q
|
|
52
|
+
Factbase::IndexedQuery.new(q, @idx, self)
|
|
55
53
|
end
|
|
56
54
|
|
|
57
55
|
# Run an ACID transaction.
|
|
@@ -15,21 +15,27 @@ class Factbase::IndexedGt
|
|
|
15
15
|
return unless @term.operands.first.is_a?(Symbol) && _scalar?(@term.operands[1])
|
|
16
16
|
prop = @term.operands.first.to_s
|
|
17
17
|
cache_key = [maps.object_id, @term.operands.first, :sorted]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
entry = @idx[cache_key]
|
|
19
|
+
maps_array = maps.to_a
|
|
20
|
+
if entry.nil?
|
|
21
|
+
entry = { sorted: [], indexed_count: 0 }
|
|
22
|
+
@idx[cache_key] = entry
|
|
23
|
+
end
|
|
24
|
+
if entry[:indexed_count] < maps_array.size
|
|
25
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
21
26
|
values = m[prop]
|
|
22
27
|
next if values.nil?
|
|
23
28
|
values.each do |v|
|
|
24
|
-
|
|
29
|
+
entry[:sorted] << [v, m]
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
|
-
|
|
32
|
+
entry[:sorted].sort_by! { |pair| pair[0] }
|
|
33
|
+
entry[:indexed_count] = maps_array.size
|
|
28
34
|
end
|
|
29
35
|
threshold = @term.operands[1].is_a?(Symbol) ? params[@term.operands[1].to_s]&.first : @term.operands[1]
|
|
30
36
|
return nil if threshold.nil?
|
|
31
|
-
i =
|
|
32
|
-
result =
|
|
37
|
+
i = entry[:sorted].bsearch_index { |pair| pair[0] > threshold } || entry[:sorted].size
|
|
38
|
+
result = entry[:sorted][i..].map { |pair| pair[1] }.uniq
|
|
33
39
|
(maps & []) | result
|
|
34
40
|
end
|
|
35
41
|
|
|
@@ -15,21 +15,32 @@ class Factbase::IndexedLt
|
|
|
15
15
|
return unless @term.operands.first.is_a?(Symbol) && _scalar?(@term.operands[1])
|
|
16
16
|
prop = @term.operands.first.to_s
|
|
17
17
|
cache_key = [maps.object_id, @term.operands.first, :sorted]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
entry = @idx[cache_key]
|
|
19
|
+
maps_array = maps.to_a
|
|
20
|
+
if entry.nil?
|
|
21
|
+
entry = { sorted: [], indexed_count: 0 }
|
|
22
|
+
@idx[cache_key] = entry
|
|
23
|
+
end
|
|
24
|
+
if entry[:indexed_count] < maps_array.size
|
|
25
|
+
new_pairs = []
|
|
26
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
21
27
|
values = m[prop]
|
|
22
28
|
next if values.nil?
|
|
23
29
|
values.each do |v|
|
|
24
|
-
|
|
30
|
+
new_pairs << [v, m]
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
|
-
|
|
33
|
+
unless new_pairs.empty?
|
|
34
|
+
entry[:sorted].concat(new_pairs)
|
|
35
|
+
entry[:sorted].sort_by! { |pair| pair[0] }
|
|
36
|
+
end
|
|
37
|
+
entry[:indexed_count] = maps_array.size
|
|
28
38
|
end
|
|
39
|
+
|
|
29
40
|
threshold = @term.operands[1].is_a?(Symbol) ? params[@term.operands[1].to_s]&.first : @term.operands[1]
|
|
30
41
|
return nil if threshold.nil?
|
|
31
|
-
i =
|
|
32
|
-
result =
|
|
42
|
+
i = entry[:sorted].bsearch_index { |pair| pair[0] >= threshold } || entry[:sorted].size
|
|
43
|
+
result = entry[:sorted][0...i].map { |pair| pair[1] }.uniq
|
|
33
44
|
(maps & []) | result
|
|
34
45
|
end
|
|
35
46
|
|
|
@@ -13,16 +13,25 @@ class Factbase::IndexedNot
|
|
|
13
13
|
def predict(maps, fb, params)
|
|
14
14
|
return nil if @idx.nil?
|
|
15
15
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
16
|
-
|
|
16
|
+
entry = @idx[key]
|
|
17
|
+
maps_array = maps.to_a
|
|
18
|
+
if entry.nil?
|
|
19
|
+
entry = { facts: nil, indexed_count: 0, yes_set: nil }
|
|
20
|
+
@idx[key] = entry
|
|
21
|
+
end
|
|
22
|
+
if entry[:indexed_count] < maps_array.size
|
|
17
23
|
yes = @term.operands.first.predict(maps, fb, params)
|
|
18
24
|
if yes.nil?
|
|
19
|
-
|
|
25
|
+
entry[:facts] = nil
|
|
26
|
+
entry[:yes_set] = nil
|
|
20
27
|
else
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
yes_set = yes.to_a.to_set
|
|
29
|
+
entry[:yes_set] = yes_set
|
|
30
|
+
entry[:facts] = maps_array.reject { |m| yes_set.include?(m) }
|
|
23
31
|
end
|
|
32
|
+
entry[:indexed_count] = maps_array.size
|
|
24
33
|
end
|
|
25
|
-
r =
|
|
34
|
+
r = entry[:facts]
|
|
26
35
|
if r.nil?
|
|
27
36
|
nil
|
|
28
37
|
else
|
|
@@ -13,13 +13,19 @@ class Factbase::IndexedOne
|
|
|
13
13
|
def predict(maps, _fb, _params)
|
|
14
14
|
return nil if @idx.nil?
|
|
15
15
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
entry = @idx[key]
|
|
17
|
+
maps_array = maps.to_a
|
|
18
|
+
if entry.nil?
|
|
19
|
+
entry = { facts: [], indexed_count: 0 }
|
|
20
|
+
@idx[key] = entry
|
|
21
|
+
end
|
|
22
|
+
if entry[:indexed_count] < maps_array.size
|
|
18
23
|
prop = @term.operands.first.to_s
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
25
|
+
entry[:facts] << m if !m[prop].nil? && m[prop].size == 1
|
|
21
26
|
end
|
|
27
|
+
entry[:indexed_count] = maps_array.size
|
|
22
28
|
end
|
|
23
|
-
(maps & []) |
|
|
29
|
+
(maps & []) | entry[:facts]
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -46,7 +46,7 @@ class Factbase::IndexedQuery
|
|
|
46
46
|
# @param [Factbase] fb The factbase
|
|
47
47
|
# @param [Hash] params Optional params accessible in the query via the "$" symbol
|
|
48
48
|
# @return [String|Integer|Float|Time|Array|NilClass] The value evaluated
|
|
49
|
-
def one(fb = @fb, params =
|
|
49
|
+
def one(fb = @fb, params = {})
|
|
50
50
|
@origin.one(fb, params)
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -54,7 +54,8 @@ class Factbase::IndexedQuery
|
|
|
54
54
|
# @param [Factbase] fb The factbase
|
|
55
55
|
# @return [Integer] Total number of facts deleted
|
|
56
56
|
def delete!(fb = @fb)
|
|
57
|
+
result = @origin.delete!(fb)
|
|
57
58
|
@idx.clear
|
|
58
|
-
|
|
59
|
+
result
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -16,10 +16,19 @@ class Factbase::IndexedUnique
|
|
|
16
16
|
def predict(maps, _fb, _params)
|
|
17
17
|
return nil if @idx.nil?
|
|
18
18
|
key = [maps.object_id, @term.operands.first, @term.op]
|
|
19
|
-
|
|
19
|
+
entry = @idx[key]
|
|
20
|
+
maps_array = maps.to_a
|
|
21
|
+
if entry.nil?
|
|
22
|
+
entry = { facts: [], indexed_count: 0 }
|
|
23
|
+
@idx[key] = entry
|
|
24
|
+
end
|
|
25
|
+
if entry[:indexed_count] < maps_array.size
|
|
20
26
|
props = @term.operands.map(&:to_s)
|
|
21
|
-
|
|
27
|
+
maps_array[entry[:indexed_count]..].each do |m|
|
|
28
|
+
entry[:facts] << m if props.all? { |p| !m[p].nil? }
|
|
29
|
+
end
|
|
30
|
+
entry[:indexed_count] = maps_array.size
|
|
22
31
|
end
|
|
23
|
-
(maps & []) |
|
|
32
|
+
(maps & []) | entry[:facts]
|
|
24
33
|
end
|
|
25
34
|
end
|
data/lib/factbase/version.rb
CHANGED