factbase 0.16.7 → 0.17.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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +25 -20
- data/README.md +28 -27
- data/Rakefile +14 -6
- data/lib/factbase/cached/cached_query.rb +2 -0
- data/lib/factbase/indexed/indexed_absent.rb +25 -0
- data/lib/factbase/indexed/indexed_and.rb +79 -0
- data/lib/factbase/indexed/indexed_eq.rb +46 -0
- data/lib/factbase/indexed/indexed_exists.rb +25 -0
- data/lib/factbase/indexed/indexed_factbase.rb +46 -0
- data/lib/factbase/indexed/indexed_gt.rb +41 -0
- data/lib/factbase/indexed/indexed_lt.rb +41 -0
- data/lib/factbase/indexed/indexed_not.rb +32 -0
- data/lib/factbase/indexed/indexed_one.rb +25 -0
- data/lib/factbase/indexed/indexed_or.rb +28 -0
- data/lib/factbase/indexed/indexed_query.rb +2 -0
- data/lib/factbase/indexed/indexed_term.rb +29 -185
- data/lib/factbase/indexed/indexed_unique.rb +25 -0
- data/lib/factbase/query.rb +22 -8
- data/lib/factbase/sync/sync_factbase.rb +11 -11
- data/lib/factbase/sync/sync_query.rb +7 -8
- data/lib/factbase/term.rb +110 -91
- data/lib/factbase/terms/absent.rb +26 -0
- data/lib/factbase/terms/aggregates.rb +0 -13
- data/lib/factbase/terms/always.rb +27 -0
- data/lib/factbase/terms/and.rb +28 -0
- data/lib/factbase/terms/arithmetic.rb +55 -0
- data/lib/factbase/terms/as.rb +31 -0
- data/lib/factbase/terms/{debug.rb → assert.rb} +17 -15
- data/lib/factbase/terms/base.rb +17 -0
- data/lib/factbase/terms/boolean.rb +28 -0
- data/lib/factbase/terms/compare.rb +38 -0
- data/lib/factbase/terms/concat.rb +26 -0
- data/lib/factbase/terms/count.rb +25 -0
- data/lib/factbase/terms/defn.rb +16 -15
- data/lib/factbase/terms/div.rb +25 -0
- data/lib/factbase/terms/either.rb +31 -0
- data/lib/factbase/terms/env.rb +28 -0
- data/lib/factbase/terms/eq.rb +28 -0
- data/lib/factbase/terms/exists.rb +27 -0
- data/lib/factbase/terms/first.rb +30 -0
- data/lib/factbase/terms/gt.rb +28 -0
- data/lib/factbase/terms/gte.rb +27 -0
- data/lib/factbase/terms/head.rb +37 -0
- data/lib/factbase/terms/inverted.rb +34 -0
- data/lib/factbase/terms/{aliases.rb → join.rb} +15 -15
- data/lib/factbase/terms/logical.rb +0 -83
- data/lib/factbase/terms/lt.rb +28 -0
- data/lib/factbase/terms/lte.rb +28 -0
- data/lib/factbase/terms/many.rb +29 -0
- data/lib/factbase/terms/{strings.rb → matches.rb} +12 -12
- data/lib/factbase/terms/minus.rb +25 -0
- data/lib/factbase/terms/never.rb +26 -0
- data/lib/factbase/terms/nil.rb +26 -0
- data/lib/factbase/terms/not.rb +27 -0
- data/lib/factbase/terms/one.rb +30 -0
- data/lib/factbase/terms/or.rb +28 -0
- data/lib/factbase/terms/plus.rb +27 -0
- data/lib/factbase/terms/prev.rb +29 -0
- data/lib/factbase/terms/shared.rb +69 -0
- data/lib/factbase/terms/size.rb +30 -0
- data/lib/factbase/terms/sorted.rb +38 -0
- data/lib/factbase/terms/sprintf.rb +29 -0
- data/lib/factbase/terms/times.rb +25 -0
- data/lib/factbase/terms/to_float.rb +28 -0
- data/lib/factbase/terms/to_integer.rb +28 -0
- data/lib/factbase/terms/to_string.rb +28 -0
- data/lib/factbase/terms/to_time.rb +28 -0
- data/lib/factbase/terms/traced.rb +33 -0
- data/lib/factbase/terms/type.rb +31 -0
- data/lib/factbase/terms/undef.rb +33 -0
- data/lib/factbase/terms/unique.rb +34 -0
- data/lib/factbase/terms/when.rb +29 -0
- data/lib/factbase/terms/zero.rb +28 -0
- data/lib/factbase/version.rb +1 -1
- data/lib/factbase.rb +10 -1
- metadata +60 -10
- data/lib/factbase/terms/casting.rb +0 -41
- data/lib/factbase/terms/lists.rb +0 -57
- data/lib/factbase/terms/math.rb +0 -103
- data/lib/factbase/terms/meta.rb +0 -58
- data/lib/factbase/terms/ordering.rb +0 -34
- data/lib/factbase/terms/system.rb +0 -19
|
@@ -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
|
+
# Indexed term 'or'.
|
|
7
|
+
class Factbase::IndexedOr
|
|
8
|
+
def initialize(term, idx)
|
|
9
|
+
@term = term
|
|
10
|
+
@idx = idx
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def predict(maps, fb, params)
|
|
14
|
+
return nil if @idx.nil?
|
|
15
|
+
r = nil
|
|
16
|
+
@term.operands.each do |o|
|
|
17
|
+
n = o.predict(maps, fb, params)
|
|
18
|
+
if n.nil?
|
|
19
|
+
r = nil
|
|
20
|
+
break
|
|
21
|
+
end
|
|
22
|
+
r = maps & [] if r.nil?
|
|
23
|
+
r |= n.to_a
|
|
24
|
+
return maps if r.size > maps.size / 4 # it's big enough already
|
|
25
|
+
end
|
|
26
|
+
r
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -12,6 +12,8 @@ require_relative 'indexed_fact'
|
|
|
12
12
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
13
13
|
# License:: MIT
|
|
14
14
|
class Factbase::IndexedQuery
|
|
15
|
+
include Enumerable
|
|
16
|
+
|
|
15
17
|
# Constructor.
|
|
16
18
|
# @param [Factbase::Query] origin Original query
|
|
17
19
|
# @param [Hash] idx The index
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
require 'tago'
|
|
7
7
|
require_relative '../../factbase'
|
|
8
|
+
require_relative '../indexed/indexed_eq'
|
|
9
|
+
require_relative '../indexed/indexed_lt'
|
|
10
|
+
require_relative '../indexed/indexed_gt'
|
|
11
|
+
require_relative '../indexed/indexed_one'
|
|
12
|
+
require_relative '../indexed/indexed_not'
|
|
13
|
+
require_relative '../indexed/indexed_exists'
|
|
14
|
+
require_relative '../indexed/indexed_and'
|
|
15
|
+
require_relative '../indexed/indexed_or'
|
|
16
|
+
require_relative '../indexed/indexed_absent'
|
|
17
|
+
require_relative '../indexed/indexed_unique'
|
|
8
18
|
|
|
9
19
|
# Term with an index.
|
|
10
20
|
#
|
|
@@ -20,196 +30,30 @@ module Factbase::IndexedTerm
|
|
|
20
30
|
# @param [Hash] params Key/value params to use
|
|
21
31
|
# @return [Array<Hash>|nil] Returns a new array, or NIL if the original array must be used
|
|
22
32
|
def predict(maps, fb, params)
|
|
33
|
+
if @terms.key?(@op)
|
|
34
|
+
t = @terms[@op]
|
|
35
|
+
return t.predict(maps, fb, params) if t.respond_to?(:predict)
|
|
36
|
+
end
|
|
23
37
|
m = :"#{@op}_predict"
|
|
24
38
|
return send(m, maps, fb, params) if respond_to?(m)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
when :one
|
|
28
|
-
if @idx[key].nil?
|
|
29
|
-
@idx[key] = []
|
|
30
|
-
prop = @operands.first.to_s
|
|
31
|
-
maps.to_a.each do |m|
|
|
32
|
-
@idx[key].append(m) if !m[prop].nil? && m[prop].size == 1
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
(maps & []) | @idx[key]
|
|
36
|
-
when :exists
|
|
37
|
-
if @idx[key].nil?
|
|
38
|
-
@idx[key] = []
|
|
39
|
-
prop = @operands.first.to_s
|
|
40
|
-
maps.to_a.each do |m|
|
|
41
|
-
@idx[key].append(m) unless m[prop].nil?
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
(maps & []) | @idx[key]
|
|
45
|
-
when :absent
|
|
46
|
-
if @idx[key].nil?
|
|
47
|
-
@idx[key] = []
|
|
48
|
-
prop = @operands.first.to_s
|
|
49
|
-
maps.to_a.each do |m|
|
|
50
|
-
@idx[key].append(m) if m[prop].nil?
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
(maps & []) | @idx[key]
|
|
54
|
-
when :eq
|
|
55
|
-
if @operands.first.is_a?(Symbol) && _scalar?(@operands[1])
|
|
56
|
-
if @idx[key].nil?
|
|
57
|
-
@idx[key] = {}
|
|
58
|
-
prop = @operands.first.to_s
|
|
59
|
-
maps.to_a.each do |m|
|
|
60
|
-
m[prop]&.each do |v|
|
|
61
|
-
@idx[key][v] = [] if @idx[key][v].nil?
|
|
62
|
-
@idx[key][v].append(m)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
vv =
|
|
67
|
-
if @operands[1].is_a?(Symbol)
|
|
68
|
-
params[@operands[1].to_s] || []
|
|
69
|
-
else
|
|
70
|
-
[@operands[1]]
|
|
71
|
-
end
|
|
72
|
-
if vv.empty?
|
|
73
|
-
(maps & [])
|
|
74
|
-
else
|
|
75
|
-
j = vv.map { |v| @idx[key][v] || [] }.reduce(&:|)
|
|
76
|
-
(maps & []) | j
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
when :gt
|
|
80
|
-
if @operands.first.is_a?(Symbol) && _scalar?(@operands[1])
|
|
81
|
-
prop = @operands.first.to_s
|
|
82
|
-
cache_key = [maps.object_id, @operands.first, :sorted]
|
|
83
|
-
if @idx[cache_key].nil?
|
|
84
|
-
@idx[cache_key] = []
|
|
85
|
-
maps.to_a.each do |m|
|
|
86
|
-
values = m[prop]
|
|
87
|
-
next if values.nil?
|
|
88
|
-
values.each do |v|
|
|
89
|
-
@idx[cache_key] << [v, m]
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
@idx[cache_key].sort_by! { |pair| pair[0] }
|
|
93
|
-
end
|
|
94
|
-
threshold = @operands[1].is_a?(Symbol) ? params[@operands[1].to_s]&.first : @operands[1]
|
|
95
|
-
return nil if threshold.nil?
|
|
96
|
-
i = @idx[cache_key].bsearch_index { |pair| pair[0] > threshold } || @idx[cache_key].size
|
|
97
|
-
result = @idx[cache_key][i..].map { |pair| pair[1] }.uniq
|
|
98
|
-
(maps & []) | result
|
|
99
|
-
end
|
|
100
|
-
when :lt
|
|
101
|
-
if @operands.first.is_a?(Symbol) && _scalar?(@operands[1])
|
|
102
|
-
prop = @operands.first.to_s
|
|
103
|
-
cache_key = [maps.object_id, @operands.first, :sorted]
|
|
104
|
-
if @idx[cache_key].nil?
|
|
105
|
-
@idx[cache_key] = []
|
|
106
|
-
maps.to_a.each do |m|
|
|
107
|
-
values = m[prop]
|
|
108
|
-
next if values.nil?
|
|
109
|
-
values.each do |v|
|
|
110
|
-
@idx[cache_key] << [v, m]
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
@idx[cache_key].sort_by! { |pair| pair[0] }
|
|
114
|
-
end
|
|
115
|
-
threshold = @operands[1].is_a?(Symbol) ? params[@operands[1].to_s]&.first : @operands[1]
|
|
116
|
-
return nil if threshold.nil?
|
|
117
|
-
i = @idx[cache_key].bsearch_index { |pair| pair[0] >= threshold } || @idx[cache_key].size
|
|
118
|
-
result = @idx[cache_key][0...i].map { |pair| pair[1] }.uniq
|
|
119
|
-
(maps & []) | result
|
|
120
|
-
end
|
|
121
|
-
when :and
|
|
122
|
-
r = nil
|
|
123
|
-
if @operands.all? { |o| o.op == :eq } && @operands.size > 1 \
|
|
124
|
-
&& @operands.all? { |o| o.operands.first.is_a?(Symbol) && _scalar?(o.operands[1]) }
|
|
125
|
-
props = @operands.map { |o| o.operands.first }.sort
|
|
126
|
-
key = [maps.object_id, props, :multi_and_eq]
|
|
127
|
-
if @idx[key].nil?
|
|
128
|
-
@idx[key] = {}
|
|
129
|
-
maps.to_a.each do |m|
|
|
130
|
-
_all_tuples(m, props).each do |t|
|
|
131
|
-
@idx[key][t] = [] if @idx[key][t].nil?
|
|
132
|
-
@idx[key][t].append(m)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
tuples = Enumerator.product(
|
|
137
|
-
*@operands.sort_by { |o| o.operands.first }.map do |o|
|
|
138
|
-
if o.operands[1].is_a?(Symbol)
|
|
139
|
-
params[o.operands[1].to_s] || []
|
|
140
|
-
else
|
|
141
|
-
[o.operands[1]]
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
)
|
|
145
|
-
j = tuples.map { |t| @idx[key][t] || [] }.reduce(&:|)
|
|
146
|
-
r = (maps & []) | j
|
|
147
|
-
else
|
|
148
|
-
@operands.each do |o|
|
|
149
|
-
n = o.predict(maps, fb, params)
|
|
150
|
-
break if n.nil?
|
|
151
|
-
if r.nil?
|
|
152
|
-
r = n
|
|
153
|
-
elsif n.size < r.size * 8 # to skip some obvious matchings
|
|
154
|
-
r &= n.to_a
|
|
155
|
-
end
|
|
156
|
-
break if r.size < maps.size / 32 # it's already small enough
|
|
157
|
-
break if r.size < 128 # it's obviously already small enough
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
r
|
|
161
|
-
when :or
|
|
162
|
-
r = nil
|
|
163
|
-
@operands.each do |o|
|
|
164
|
-
n = o.predict(maps, fb, params)
|
|
165
|
-
if n.nil?
|
|
166
|
-
r = nil
|
|
167
|
-
break
|
|
168
|
-
end
|
|
169
|
-
r = maps & [] if r.nil?
|
|
170
|
-
r |= n.to_a
|
|
171
|
-
return maps if r.size > maps.size / 4 # it's big enough already
|
|
172
|
-
end
|
|
173
|
-
r
|
|
174
|
-
when :not
|
|
175
|
-
if @idx[key].nil?
|
|
176
|
-
yes = @operands.first.predict(maps, fb, params)
|
|
177
|
-
if yes.nil?
|
|
178
|
-
@idx[key] = { r: nil }
|
|
179
|
-
else
|
|
180
|
-
yes = yes.to_a.to_set
|
|
181
|
-
@idx[key] = { r: maps.to_a.reject { |m| yes.include?(m) } }
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
r = @idx[key][:r]
|
|
185
|
-
if r.nil?
|
|
186
|
-
nil
|
|
187
|
-
else
|
|
188
|
-
(maps & []) | r
|
|
189
|
-
end
|
|
190
|
-
end
|
|
39
|
+
_init_indexes unless @indexes
|
|
40
|
+
@indexes[@op].predict(maps, fb, params) if @indexes.key?(@op)
|
|
191
41
|
end
|
|
192
42
|
|
|
193
43
|
private
|
|
194
44
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end
|
|
209
|
-
tuples
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def _scalar?(item)
|
|
213
|
-
item.is_a?(String) || item.is_a?(Time) || item.is_a?(Integer) || item.is_a?(Float) || item.is_a?(Symbol)
|
|
45
|
+
def _init_indexes
|
|
46
|
+
@indexes = {
|
|
47
|
+
eq: Factbase::IndexedEq.new(self, @idx),
|
|
48
|
+
lt: Factbase::IndexedLt.new(self, @idx),
|
|
49
|
+
gt: Factbase::IndexedGt.new(self, @idx),
|
|
50
|
+
one: Factbase::IndexedOne.new(self, @idx),
|
|
51
|
+
exists: Factbase::IndexedExists.new(self, @idx),
|
|
52
|
+
absent: Factbase::IndexedAbsent.new(self, @idx),
|
|
53
|
+
unique: Factbase::IndexedUnique.new(self, @idx),
|
|
54
|
+
and: Factbase::IndexedAnd.new(self, @idx),
|
|
55
|
+
not: Factbase::IndexedNot.new(self, @idx),
|
|
56
|
+
or: Factbase::IndexedOr.new(self, @idx)
|
|
57
|
+
}
|
|
214
58
|
end
|
|
215
59
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Indexed term 'unique'.
|
|
7
|
+
# @todo #249:30min Improve prediction for 'unique' term. Current prediction is quite naive and
|
|
8
|
+
# returns many false positives because it just filters facts which have exactly the same set
|
|
9
|
+
# of keys regardless the values. We should introduce more smart prediction.
|
|
10
|
+
class Factbase::IndexedUnique
|
|
11
|
+
def initialize(term, idx)
|
|
12
|
+
@term = term
|
|
13
|
+
@idx = idx
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def predict(maps, _fb, _params)
|
|
17
|
+
return nil if @idx.nil?
|
|
18
|
+
key = [maps.object_id, @term.operands.first, @term.op]
|
|
19
|
+
if @idx[key].nil?
|
|
20
|
+
props = @term.operands.map(&:to_s)
|
|
21
|
+
@idx[key] = maps.to_a.select { |m| props.all? { |p| !m[p].nil? } }
|
|
22
|
+
end
|
|
23
|
+
(maps & []) | @idx[key]
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/factbase/query.rb
CHANGED
|
@@ -20,6 +20,8 @@ require_relative 'tee'
|
|
|
20
20
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
21
21
|
# License:: MIT
|
|
22
22
|
class Factbase::Query
|
|
23
|
+
include Enumerable
|
|
24
|
+
|
|
23
25
|
# Constructor.
|
|
24
26
|
# @param [Array<Fact>] maps Array of facts to start with
|
|
25
27
|
# @param [String|Factbase::Term] term The query term
|
|
@@ -77,17 +79,29 @@ class Factbase::Query
|
|
|
77
79
|
|
|
78
80
|
# Delete all facts that match the query.
|
|
79
81
|
# @param [Factbase] fb The factbase to delete from
|
|
82
|
+
# @param [String] id The id of facts that uniquely identify them
|
|
80
83
|
# @return [Integer] Total number of facts deleted
|
|
81
|
-
def delete!(fb = @fb)
|
|
84
|
+
def delete!(fb = @fb, id: '_id')
|
|
82
85
|
deleted = 0
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
false
|
|
86
|
+
ids = []
|
|
87
|
+
each(fb) do |f|
|
|
88
|
+
i = f[id]
|
|
89
|
+
unless i
|
|
90
|
+
ids = nil
|
|
91
|
+
break
|
|
90
92
|
end
|
|
93
|
+
ids << i.first
|
|
94
|
+
end
|
|
95
|
+
@maps.delete_if do |m|
|
|
96
|
+
d =
|
|
97
|
+
if ids
|
|
98
|
+
i = m[id]&.first
|
|
99
|
+
i && ids.include?(i)
|
|
100
|
+
else
|
|
101
|
+
@term.evaluate(Factbase::Fact.new(m), @maps, fb)
|
|
102
|
+
end
|
|
103
|
+
deleted += 1 if d
|
|
104
|
+
d
|
|
91
105
|
end
|
|
92
106
|
deleted
|
|
93
107
|
end
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
require 'decoor'
|
|
7
|
+
require 'monitor'
|
|
7
8
|
require_relative '../../factbase'
|
|
8
9
|
|
|
9
10
|
# A synchronous thread-safe factbase.
|
|
@@ -16,10 +17,10 @@ class Factbase::SyncFactbase
|
|
|
16
17
|
|
|
17
18
|
# Constructor.
|
|
18
19
|
# @param [Factbase] origin Original factbase to decorate
|
|
19
|
-
# @param [
|
|
20
|
-
def initialize(origin,
|
|
20
|
+
# @param [Monitor] monitor Monitor to use for synchronization
|
|
21
|
+
def initialize(origin, monitor = Monitor.new)
|
|
21
22
|
@origin = origin
|
|
22
|
-
@
|
|
23
|
+
@monitor = monitor
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Insert a new fact and return it.
|
|
@@ -43,24 +44,23 @@ class Factbase::SyncFactbase
|
|
|
43
44
|
def query(term, maps = nil)
|
|
44
45
|
term = to_term(term) if term.is_a?(String)
|
|
45
46
|
require_relative 'sync_query'
|
|
46
|
-
Factbase::SyncQuery.new(@origin.query(term, maps), @
|
|
47
|
+
Factbase::SyncQuery.new(@origin.query(term, maps), @monitor, self)
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
# Run an ACID transaction.
|
|
50
51
|
# @return [Factbase::Churn] How many facts have been changed (zero if rolled back)
|
|
51
52
|
# @yield [Factbase] Block to execute in transaction
|
|
52
53
|
def txn
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
try_lock do
|
|
55
|
+
@origin.txn do |fbt|
|
|
56
|
+
yield Factbase::SyncFactbase.new(fbt, @monitor)
|
|
57
|
+
end
|
|
55
58
|
end
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
private
|
|
59
62
|
|
|
60
|
-
def try_lock
|
|
61
|
-
|
|
62
|
-
r = yield
|
|
63
|
-
@mutex.unlock if locked
|
|
64
|
-
r
|
|
63
|
+
def try_lock(&)
|
|
64
|
+
@monitor.synchronize(&)
|
|
65
65
|
end
|
|
66
66
|
end
|
|
@@ -11,12 +11,14 @@ require_relative '../../factbase'
|
|
|
11
11
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
12
12
|
# License:: MIT
|
|
13
13
|
class Factbase::SyncQuery
|
|
14
|
+
include Enumerable
|
|
15
|
+
|
|
14
16
|
# Constructor.
|
|
15
17
|
# @param [Factbase::Query] origin Original query
|
|
16
|
-
# @param [
|
|
17
|
-
def initialize(origin,
|
|
18
|
+
# @param [Monitor] monitor The monitor
|
|
19
|
+
def initialize(origin, monitor, fb)
|
|
18
20
|
@origin = origin
|
|
19
|
-
@
|
|
21
|
+
@monitor = monitor
|
|
20
22
|
@fb = fb
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -57,10 +59,7 @@ class Factbase::SyncQuery
|
|
|
57
59
|
|
|
58
60
|
private
|
|
59
61
|
|
|
60
|
-
def try_lock
|
|
61
|
-
|
|
62
|
-
r = yield
|
|
63
|
-
@mutex.unlock if locked
|
|
64
|
-
r
|
|
62
|
+
def try_lock(&)
|
|
63
|
+
@monitor.synchronize(&)
|
|
65
64
|
end
|
|
66
65
|
end
|
data/lib/factbase/term.rb
CHANGED
|
@@ -7,6 +7,51 @@ require 'backtrace'
|
|
|
7
7
|
require_relative '../factbase'
|
|
8
8
|
require_relative 'fact'
|
|
9
9
|
require_relative 'tee'
|
|
10
|
+
require_relative 'terms/unique'
|
|
11
|
+
require_relative 'terms/prev'
|
|
12
|
+
require_relative 'terms/concat'
|
|
13
|
+
require_relative 'terms/sprintf'
|
|
14
|
+
require_relative 'terms/matches'
|
|
15
|
+
require_relative 'terms/traced'
|
|
16
|
+
require_relative 'terms/assert'
|
|
17
|
+
require_relative 'terms/env'
|
|
18
|
+
require_relative 'terms/defn'
|
|
19
|
+
require_relative 'terms/undef'
|
|
20
|
+
require_relative 'terms/as'
|
|
21
|
+
require_relative 'terms/join'
|
|
22
|
+
require_relative 'terms/exists'
|
|
23
|
+
require_relative 'terms/absent'
|
|
24
|
+
require_relative 'terms/size'
|
|
25
|
+
require_relative 'terms/type'
|
|
26
|
+
require_relative 'terms/nil'
|
|
27
|
+
require_relative 'terms/many'
|
|
28
|
+
require_relative 'terms/one'
|
|
29
|
+
require_relative 'terms/to_string'
|
|
30
|
+
require_relative 'terms/to_integer'
|
|
31
|
+
require_relative 'terms/to_float'
|
|
32
|
+
require_relative 'terms/to_time'
|
|
33
|
+
require_relative 'terms/sorted'
|
|
34
|
+
require_relative 'terms/inverted'
|
|
35
|
+
require_relative 'terms/head'
|
|
36
|
+
require_relative 'terms/plus'
|
|
37
|
+
require_relative 'terms/minus'
|
|
38
|
+
require_relative 'terms/times'
|
|
39
|
+
require_relative 'terms/div'
|
|
40
|
+
require_relative 'terms/zero'
|
|
41
|
+
require_relative 'terms/eq'
|
|
42
|
+
require_relative 'terms/lt'
|
|
43
|
+
require_relative 'terms/lte'
|
|
44
|
+
require_relative 'terms/gt'
|
|
45
|
+
require_relative 'terms/gte'
|
|
46
|
+
require_relative 'terms/always'
|
|
47
|
+
require_relative 'terms/never'
|
|
48
|
+
require_relative 'terms/not'
|
|
49
|
+
require_relative 'terms/or'
|
|
50
|
+
require_relative 'terms/and'
|
|
51
|
+
require_relative 'terms/when'
|
|
52
|
+
require_relative 'terms/either'
|
|
53
|
+
require_relative 'terms/count'
|
|
54
|
+
require_relative 'terms/first'
|
|
10
55
|
|
|
11
56
|
# Term.
|
|
12
57
|
#
|
|
@@ -44,41 +89,14 @@ class Factbase::Term
|
|
|
44
89
|
# @return [Array] The operands
|
|
45
90
|
attr_reader :operands
|
|
46
91
|
|
|
47
|
-
require_relative 'terms/math'
|
|
48
|
-
include Factbase::Math
|
|
49
|
-
|
|
50
92
|
require_relative 'terms/logical'
|
|
51
93
|
include Factbase::Logical
|
|
52
94
|
|
|
53
95
|
require_relative 'terms/aggregates'
|
|
54
96
|
include Factbase::Aggregates
|
|
55
97
|
|
|
56
|
-
require_relative 'terms/
|
|
57
|
-
include Factbase::
|
|
58
|
-
|
|
59
|
-
require_relative 'terms/strings'
|
|
60
|
-
include Factbase::Strings
|
|
61
|
-
|
|
62
|
-
require_relative 'terms/casting'
|
|
63
|
-
include Factbase::Casting
|
|
64
|
-
|
|
65
|
-
require_relative 'terms/meta'
|
|
66
|
-
include Factbase::Meta
|
|
67
|
-
|
|
68
|
-
require_relative 'terms/aliases'
|
|
69
|
-
include Factbase::Aliases
|
|
70
|
-
|
|
71
|
-
require_relative 'terms/ordering'
|
|
72
|
-
include Factbase::Ordering
|
|
73
|
-
|
|
74
|
-
require_relative 'terms/defn'
|
|
75
|
-
include Factbase::Defn
|
|
76
|
-
|
|
77
|
-
require_relative 'terms/system'
|
|
78
|
-
include Factbase::System
|
|
79
|
-
|
|
80
|
-
require_relative 'terms/debug'
|
|
81
|
-
include Factbase::Debug
|
|
98
|
+
require_relative 'terms/shared'
|
|
99
|
+
include Factbase::TermShared
|
|
82
100
|
|
|
83
101
|
# Ctor.
|
|
84
102
|
# @param [Symbol] operator Operator
|
|
@@ -86,6 +104,53 @@ class Factbase::Term
|
|
|
86
104
|
def initialize(operator, operands)
|
|
87
105
|
@op = operator
|
|
88
106
|
@operands = operands
|
|
107
|
+
@terms = {
|
|
108
|
+
unique: Factbase::Unique.new(operands),
|
|
109
|
+
prev: Factbase::Prev.new(operands),
|
|
110
|
+
concat: Factbase::Concat.new(operands),
|
|
111
|
+
sprintf: Factbase::Sprintf.new(operands),
|
|
112
|
+
matches: Factbase::Matches.new(operands),
|
|
113
|
+
traced: Factbase::Traced.new(operands),
|
|
114
|
+
assert: Factbase::Assert.new(operands),
|
|
115
|
+
env: Factbase::Env.new(operands),
|
|
116
|
+
defn: Factbase::Defn.new(operands),
|
|
117
|
+
undef: Factbase::Undef.new(operands),
|
|
118
|
+
as: Factbase::As.new(operands),
|
|
119
|
+
join: Factbase::Join.new(operands),
|
|
120
|
+
exists: Factbase::Exists.new(operands),
|
|
121
|
+
absent: Factbase::Absent.new(operands),
|
|
122
|
+
size: Factbase::Size.new(operands),
|
|
123
|
+
type: Factbase::Type.new(operands),
|
|
124
|
+
nil: Factbase::Nil.new(operands),
|
|
125
|
+
many: Factbase::Many.new(operands),
|
|
126
|
+
one: Factbase::One.new(operands),
|
|
127
|
+
to_string: Factbase::ToString.new(operands),
|
|
128
|
+
to_integer: Factbase::ToInteger.new(operands),
|
|
129
|
+
to_float: Factbase::ToFloat.new(operands),
|
|
130
|
+
to_time: Factbase::ToTime.new(operands),
|
|
131
|
+
sorted: Factbase::Sorted.new(operands),
|
|
132
|
+
inverted: Factbase::Inverted.new(operands),
|
|
133
|
+
head: Factbase::Head.new(operands),
|
|
134
|
+
plus: Factbase::Plus.new(operands),
|
|
135
|
+
minus: Factbase::Minus.new(operands),
|
|
136
|
+
times: Factbase::Times.new(operands),
|
|
137
|
+
div: Factbase::Div.new(operands),
|
|
138
|
+
zero: Factbase::Zero.new(operands),
|
|
139
|
+
eq: Factbase::Eq.new(operands),
|
|
140
|
+
lt: Factbase::Lt.new(operands),
|
|
141
|
+
lte: Factbase::Lte.new(operands),
|
|
142
|
+
gt: Factbase::Gt.new(operands),
|
|
143
|
+
gte: Factbase::Gte.new(operands),
|
|
144
|
+
always: Factbase::Always.new(operands),
|
|
145
|
+
never: Factbase::Never.new(operands),
|
|
146
|
+
not: Factbase::Not.new(operands),
|
|
147
|
+
or: Factbase::Or.new(operands),
|
|
148
|
+
and: Factbase::And.new(operands),
|
|
149
|
+
when: Factbase::When.new(operands),
|
|
150
|
+
either: Factbase::Either.new(operands),
|
|
151
|
+
count: Factbase::Count.new(operands),
|
|
152
|
+
first: Factbase::First.new(operands)
|
|
153
|
+
}
|
|
89
154
|
end
|
|
90
155
|
|
|
91
156
|
# Extend it with the module.
|
|
@@ -108,24 +173,35 @@ class Factbase::Term
|
|
|
108
173
|
# should be evaluated. If no prediction can be made,
|
|
109
174
|
# the same list is returned.
|
|
110
175
|
# @param [Array<Hash>] maps Records to iterate, maybe
|
|
111
|
-
# @param [Hash]
|
|
176
|
+
# @param [Hash] params Params to use (keys must be strings, not symbols, with values as arrays)
|
|
112
177
|
# @return [Array<Hash>] Records to iterate
|
|
113
178
|
def predict(maps, fb, params)
|
|
114
179
|
m = :"#{@op}_predict"
|
|
115
|
-
if
|
|
180
|
+
if @terms.key?(@op)
|
|
181
|
+
t = @terms[@op]
|
|
182
|
+
if t.respond_to?(:predict)
|
|
183
|
+
t.predict(maps, fb, params)
|
|
184
|
+
else
|
|
185
|
+
maps
|
|
186
|
+
end
|
|
187
|
+
elsif respond_to?(m)
|
|
116
188
|
send(m, maps, fb, params)
|
|
117
189
|
else
|
|
118
190
|
maps
|
|
119
191
|
end
|
|
120
192
|
end
|
|
121
193
|
|
|
122
|
-
#
|
|
194
|
+
# Evaluate term on a fact
|
|
123
195
|
# @param [Factbase::Fact] fact The fact
|
|
124
196
|
# @param [Array<Factbase::Fact>] maps All maps available
|
|
125
197
|
# @param [Factbase] fb Factbase to use for sub-queries
|
|
126
|
-
# @return [
|
|
198
|
+
# @return [Object] The result of evaluation
|
|
127
199
|
def evaluate(fact, maps, fb)
|
|
128
|
-
|
|
200
|
+
if @terms.key?(@op)
|
|
201
|
+
@terms[@op].evaluate(fact, maps, fb)
|
|
202
|
+
else
|
|
203
|
+
send(@op, fact, maps, fb)
|
|
204
|
+
end
|
|
129
205
|
rescue NoMethodError => e
|
|
130
206
|
raise "Probably the term '#{@op}' is not defined at #{self}: #{e.message}"
|
|
131
207
|
rescue StandardError => e
|
|
@@ -169,27 +245,6 @@ class Factbase::Term
|
|
|
169
245
|
false
|
|
170
246
|
end
|
|
171
247
|
|
|
172
|
-
# Turns it into a string.
|
|
173
|
-
# @return [String] The string of it
|
|
174
|
-
def to_s
|
|
175
|
-
@to_s ||=
|
|
176
|
-
begin
|
|
177
|
-
items = []
|
|
178
|
-
items << @op
|
|
179
|
-
items +=
|
|
180
|
-
@operands.map do |o|
|
|
181
|
-
if o.is_a?(String)
|
|
182
|
-
"'#{o.gsub("'", "\\\\'").gsub('"', '\\\\"')}'"
|
|
183
|
-
elsif o.is_a?(Time)
|
|
184
|
-
o.utc.iso8601
|
|
185
|
-
else
|
|
186
|
-
o.to_s
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
"(#{items.join(' ')})"
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
248
|
def at(fact, maps, fb)
|
|
194
249
|
assert_args(2)
|
|
195
250
|
i = _values(0, fact, maps, fb)
|
|
@@ -200,40 +255,4 @@ class Factbase::Term
|
|
|
200
255
|
return nil if v.nil?
|
|
201
256
|
v[i]
|
|
202
257
|
end
|
|
203
|
-
|
|
204
|
-
private
|
|
205
|
-
|
|
206
|
-
def assert_args(num)
|
|
207
|
-
c = @operands.size
|
|
208
|
-
raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
|
|
209
|
-
raise "Too few (#{c}) operands for '#{@op}' (#{num} expected)" if c < num
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def _by_symbol(pos, fact)
|
|
213
|
-
o = @operands[pos]
|
|
214
|
-
raise "A symbol expected at ##{pos}, but '#{o}' (#{o.class}) provided" unless o.is_a?(Symbol)
|
|
215
|
-
k = o.to_s
|
|
216
|
-
fact[k]
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# @return [Array|nil] Either array of values or NIL
|
|
220
|
-
def _values(pos, fact, maps, fb)
|
|
221
|
-
v = @operands[pos]
|
|
222
|
-
v = v.evaluate(fact, maps, fb) if v.is_a?(Factbase::Term)
|
|
223
|
-
v = fact[v.to_s] if v.is_a?(Symbol)
|
|
224
|
-
return v if v.nil?
|
|
225
|
-
unless v.is_a?(Array)
|
|
226
|
-
v =
|
|
227
|
-
if v.respond_to?(:each)
|
|
228
|
-
v.to_a
|
|
229
|
-
else
|
|
230
|
-
[v]
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
raise 'Why not array?' unless v.is_a?(Array)
|
|
234
|
-
unless v.all? { |i| [Float, Integer, String, Time, TrueClass, FalseClass].any? { |t| i.is_a?(t) } }
|
|
235
|
-
raise 'Wrong type inside'
|
|
236
|
-
end
|
|
237
|
-
v
|
|
238
|
-
end
|
|
239
258
|
end
|