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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +13 -13
- data/README.md +24 -24
- data/REUSE.toml +7 -2
- data/Rakefile +8 -1
- data/benchmark/bench_factbase.rb +1 -1
- data/fixtures/stories/agg.yml +17 -0
- data/fixtures/stories/always.yml +16 -0
- data/fixtures/stories/as.yml +16 -0
- data/fixtures/stories/count.yml +18 -0
- data/fixtures/stories/eq.yml +30 -0
- data/fixtures/stories/gt.yml +18 -0
- data/fixtures/stories/join.yml +19 -0
- data/fixtures/stories/max.yml +14 -0
- data/fixtures/stories/min.yml +14 -0
- data/fixtures/stories/nth.yml +14 -0
- data/fixtures/stories/or.yml +18 -0
- data/fixtures/stories/sprintf.yml +12 -0
- data/fixtures/stories/sum.yml +14 -0
- data/lib/factbase/cached/cached_fact.rb +28 -0
- data/lib/factbase/cached/cached_factbase.rb +64 -0
- data/lib/factbase/cached/cached_query.rb +61 -0
- data/lib/factbase/cached/cached_term.rb +25 -0
- data/lib/factbase/fact.rb +13 -13
- data/lib/factbase/indexed/indexed_fact.rb +28 -0
- data/lib/factbase/indexed/indexed_factbase.rb +64 -0
- data/lib/factbase/indexed/indexed_query.rb +56 -0
- data/lib/factbase/indexed/indexed_term.rb +60 -0
- data/lib/factbase/inv.rb +18 -6
- data/lib/factbase/light.rb +7 -6
- data/lib/factbase/logged.rb +87 -62
- data/lib/factbase/query.rb +29 -34
- data/lib/factbase/rules.rb +16 -15
- data/lib/factbase/sync/sync_factbase.rb +57 -0
- data/lib/factbase/sync/sync_query.rb +61 -0
- data/lib/factbase/syntax.rb +12 -25
- data/lib/factbase/tallied.rb +11 -10
- data/lib/factbase/taped.rb +8 -0
- data/lib/factbase/tee.rb +2 -0
- data/lib/factbase/term.rb +45 -17
- data/lib/factbase/terms/aggregates.rb +17 -15
- data/lib/factbase/terms/aliases.rb +4 -4
- data/lib/factbase/terms/casting.rb +8 -8
- data/lib/factbase/terms/debug.rb +2 -2
- data/lib/factbase/terms/defn.rb +3 -3
- data/lib/factbase/terms/logical.rb +53 -14
- data/lib/factbase/terms/math.rb +26 -26
- data/lib/factbase/terms/meta.rb +14 -14
- data/lib/factbase/terms/ordering.rb +4 -4
- data/lib/factbase/terms/strings.rb +8 -8
- data/lib/factbase/terms/system.rb +3 -3
- data/lib/factbase.rb +67 -55
- data/test/factbase/cached/test_cached_factbase.rb +22 -0
- data/test/factbase/cached/test_cached_query.rb +79 -0
- data/test/factbase/indexed/test_indexed_query.rb +175 -0
- data/test/factbase/sync/test_sync_query.rb +30 -0
- data/test/factbase/terms/test_aggregates.rb +5 -5
- data/test/factbase/terms/test_aliases.rb +7 -7
- data/test/factbase/terms/test_casting.rb +8 -8
- data/test/factbase/terms/test_debug.rb +6 -6
- data/test/factbase/terms/test_defn.rb +14 -14
- data/test/factbase/terms/test_logical.rb +17 -19
- data/test/factbase/terms/test_math.rb +63 -61
- data/test/factbase/terms/test_meta.rb +36 -36
- data/test/factbase/terms/test_ordering.rb +9 -9
- data/test/factbase/terms/test_strings.rb +10 -10
- data/test/factbase/terms/test_system.rb +6 -6
- data/test/factbase/test_accum.rb +5 -5
- data/test/factbase/test_fact.rb +12 -12
- data/test/factbase/test_logged.rb +7 -0
- data/test/factbase/test_query.rb +110 -37
- data/test/factbase/test_rules.rb +1 -1
- data/test/factbase/test_syntax.rb +12 -12
- data/test/factbase/test_tee.rb +8 -8
- data/test/factbase/test_term.rb +39 -30
- data/test/test__helper.rb +2 -2
- data/test/test_factbase.rb +6 -0
- metadata +29 -4
- data/lib/factbase/query_once.rb +0 -54
- 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
|
14
|
-
#
|
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(
|
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(
|
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
|
-
@
|
63
|
-
|
64
|
-
|
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
|
data/lib/factbase/light.rb
CHANGED
@@ -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
|
-
|
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
|
30
|
-
@fb.
|
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
|
data/lib/factbase/logged.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
21
|
-
|
22
|
-
|
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(:
|
34
|
+
decoor(:origin)
|
26
35
|
|
27
36
|
def insert
|
28
37
|
start = Time.now
|
29
|
-
f = @
|
30
|
-
@
|
31
|
-
Fact.new(f, @
|
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(
|
35
|
-
|
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
|
-
@
|
53
|
+
@origin.txn do |fbt|
|
44
54
|
id = fbt.object_id
|
45
|
-
yield Factbase::Logged.new(fbt, @
|
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
|
-
|
61
|
+
@tube.say(start, "Txn ##{id} rolled back in #{start.ago}")
|
52
62
|
else
|
53
|
-
|
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,
|
92
|
+
def initialize(fact, tube: nil, log: nil)
|
66
93
|
@fact = fact
|
67
|
-
@
|
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
|
-
@
|
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(
|
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
|
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(@
|
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(@
|
142
|
+
r = @fb.query(@term, @maps).each(@fb, params, &)
|
125
143
|
end
|
126
|
-
raise ".each of #{@
|
144
|
+
raise ".each of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
|
127
145
|
if r.zero?
|
128
|
-
|
146
|
+
@tube.say(start, "Nothing found by '#{q}' #{tail}")
|
129
147
|
else
|
130
|
-
|
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(@
|
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
|
-
|
160
|
+
@tube.say(start, "Nothing found by '#{q}' #{tail}")
|
143
161
|
else
|
144
|
-
|
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
|
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(@
|
190
|
+
r = @fb.query(@term).delete!(@fb)
|
157
191
|
end
|
158
|
-
raise ".delete! of #{@
|
192
|
+
raise ".delete! of #{@term.class} returned #{r.class}" unless r.is_a?(Integer)
|
159
193
|
if before.zero?
|
160
|
-
|
194
|
+
@tube.say(start, "There were no facts, nothing deleted by '#{@term}' #{tail}")
|
161
195
|
elsif r.zero?
|
162
|
-
|
196
|
+
@tube.say(start, "No facts out of #{before} deleted by '#{@term}' #{tail}")
|
163
197
|
else
|
164
|
-
|
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
|