factbase 0.0.38 → 0.0.39

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3c7e0abf7bec6e53f050b4eeaeddd2b9954ee77b2a5f9a0332394c995d91ba9
4
- data.tar.gz: 5da4b8574d7b1a93fd67f3d833bf51952eb5b3d9c303b9e20a084dc5953d71bc
3
+ metadata.gz: 8ff1db55d5a25e5036d3a98423caedc960f8323681e2227c82e0a397727067c0
4
+ data.tar.gz: b3000b708bd2eb69005998a722011a296384d5cea66641b975471e611b93e4d0
5
5
  SHA512:
6
- metadata.gz: f472603e32966c098979c2a461d94cd60f1f6213c4cea4abae225694d59ae88df252bbd42dcfb3292efb97352618cfa35fe56bef86bf87310dc74b4dbce65a20
7
- data.tar.gz: fb91b271a5d57c59268dd386f8960e1c3c3c1577ea80e5c9bc3b623eaa6c559b15ce326f23ec9035e795d08ce647af353f4ae12aef5a34a124286f41fdf79130
6
+ metadata.gz: a377a359a877b1f50c83f13c451e095036b4087cf88e39c04a0e5d64e86337bfef458883646009daff4b2b90446abbdbd563a4921b3ed275b88b7408b69ef0de
7
+ data.tar.gz: 26a5ca088df353e0c526080213aa4d87b23805dd522617d52d2d738b1881c40ae4c659fa886db4fbe19a30a131ff6b5a7e19aadf5dbafcbd0f6b81954386bd65
data/README.md CHANGED
@@ -54,7 +54,8 @@ f2.import(File.read(file))
54
54
  assert(f2.query('(eq foo 42)').each.to_a.size == 1)
55
55
  ```
56
56
 
57
- There are some terms available in a query:
57
+ There are some boolean terms available in a query
58
+ (they return either TRUE or FALSE):
58
59
 
59
60
  * `(always)` and `(never)` are "true" and "false"
60
61
  * `(not t)` inverses the `t` if it's boolean (exception otherwise)
@@ -66,14 +67,16 @@ There are some terms available in a query:
66
67
  * `(eq a b)` returns true if `a` equals to `b`
67
68
  * `(lt a b)` returns true if `a` is less than `b`
68
69
  * `(gt a b)` returns true if `a` is greater than `b`
69
- * `(size k)` returns cardinality of `k` property (zero if property is absent)
70
- * `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
71
70
  * `(many a)` return true if there are many values in the `a` property
72
71
  * `(one a)` returns true if there is only one value in the `a` property
72
+ * `(matches a re)` returns true when `a` matches regular expression `re`
73
+
74
+ There are a few terms that return non-boolean values:
75
+
73
76
  * `(at i a)` returns the `i`-th value of the `a` property
77
+ * `(size k)` returns cardinality of `k` property (zero if property is absent)
78
+ * `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
74
79
  * `(either a b)` returns `b` if `a` is `nil`
75
- * `(matches a re)` returns true when `a` matches regular expression `re`
76
- * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
77
80
 
78
81
  Also, some simple arithmetic:
79
82
 
@@ -82,6 +85,10 @@ Also, some simple arithmetic:
82
85
  * `(times a b)` is a multiplication of `a` and `b`
83
86
  * `(div a b)` is a division of `a` by `b`
84
87
 
88
+ One term is for meta-programming:
89
+
90
+ * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
91
+
85
92
  There are terms that are history of search aware:
86
93
 
87
94
  * `(prev a)` returns the value of `a` in the previously seen fact
data/lib/factbase.rb CHANGED
@@ -23,9 +23,12 @@
23
23
  require 'json'
24
24
  require 'yaml'
25
25
 
26
- # Factbase.
26
+ # A factbase, which essentially is a NoSQL one-table in-memory database
27
+ # with a Lisp-ish query interface.
27
28
  #
28
- # This is an entry point to a factbase:
29
+ # This class is an entry point to a factbase. For example, this is how you
30
+ # add a new "fact" to a factbase, then put two properties into it, and then
31
+ # find this fact with a simple search.
29
32
  #
30
33
  # fb = Factbase.new
31
34
  # f = fb.insert # new fact created
@@ -34,14 +37,41 @@ require 'yaml'
34
37
  # found = f.query('(gt 20 age)').each.to_a[0]
35
38
  # assert(found.age == 42)
36
39
  #
40
+ # Every fact is a key-value hash map. Every value is a non-empty set of values.
41
+ # Consider this example of creating a factbase with a single fact inside:
42
+ #
43
+ # fb = Factbase.new
44
+ # f = fb.insert
45
+ # f.name = 'Jeff'
46
+ # f.name = 'Walter'
47
+ # f.age = 42
48
+ # f.age = 'unknown'
49
+ # f.place = 'LA'
50
+ # puts f.to_json
51
+ #
52
+ # This will print the following JSON:
53
+ #
54
+ # {
55
+ # 'name': ['Jeff', 'Walter'],
56
+ # 'age': [42, 'unknown'],
57
+ # 'place: 'LA'
58
+ # }
59
+ #
60
+ # Value sets, as you can see, allow data of different types. However, there
61
+ # are only four types are allowed: Integer, Float, String, and Time.
62
+ #
37
63
  # A factbase may be exported to a file and then imported back:
38
64
  #
39
65
  # fb1 = Factbase.new
40
- # File.writebin(file, fb1.export)
66
+ # File.binwrite(file, fb1.export)
41
67
  # fb2 = Factbase.new # it's empty
42
- # fb2.import(File.readbin(file))
68
+ # fb2.import(File.binread(file))
43
69
  #
44
- # It's important to use +writebin+ and +readbin+, because the content is
70
+ # It's impossible to delete properties of a fact. It is however possible to
71
+ # delete the entire fact, with the help of the +query()+ and then +delete!()+
72
+ # methods.
73
+ #
74
+ # It's important to use +binwrite+ and +binread+, because the content is
45
75
  # a chain of bytes, not a text.
46
76
  #
47
77
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -49,7 +79,10 @@ require 'yaml'
49
79
  # License:: MIT
50
80
  class Factbase
51
81
  # Current version of the gem (changed by .rultor.yml on every release)
52
- VERSION = '0.0.38'
82
+ VERSION = '0.0.39'
83
+
84
+ # An exception that may be thrown in a transaction, to roll it back.
85
+ class Rollback < StandardError; end
53
86
 
54
87
  # Constructor.
55
88
  def initialize(facts = [])
@@ -69,7 +102,13 @@ class Factbase
69
102
  @maps.size
70
103
  end
71
104
 
72
- # Insert a new fact.
105
+ # Insert a new fact and return it.
106
+ #
107
+ # A fact, when inserted, is empty. It doesn't contain any properties.
108
+ #
109
+ # The operation is thread-safe, meaning that you different threads may
110
+ # insert facts parallel without breaking the consistency of the factbase.
111
+ #
73
112
  # @return [Factbase::Fact] The fact just inserted
74
113
  def insert
75
114
  require_relative 'factbase/fact'
@@ -94,6 +133,9 @@ class Factbase
94
133
  # (gt bar 200)
95
134
  # (absent zzz)))
96
135
  #
136
+ # The full list of terms available in the query you can find in the
137
+ # +README.md+ file of the repository.
138
+ #
97
139
  # @param [String] query The query to use for selections
98
140
  def query(query)
99
141
  require_relative 'factbase/query'
@@ -102,9 +144,27 @@ class Factbase
102
144
 
103
145
  # Run an ACID transaction, which will either modify the factbase
104
146
  # or rollback in case of an error.
147
+ #
148
+ # If necessary to terminate a transaction and roolback all changes,
149
+ # you should raise the +Factbase::Rollback+ exception:
150
+ #
151
+ # fb = Factbase.new
152
+ # fb.txn do |fbt|
153
+ # fbt.insert.bar = 42
154
+ # raise Factbase::Rollback
155
+ # end
156
+ #
157
+ # A the end of this script, the factbase will be empty. No facts will
158
+ # inserted and all changes that happened in the block will be rolled back.
159
+ #
160
+ # @param [Factbase] this The factbase to use (don't provide this param)
105
161
  def txn(this = self)
106
162
  copy = this.dup
107
- yield copy
163
+ begin
164
+ yield copy
165
+ rescue Factbase::Rollback
166
+ return
167
+ end
108
168
  @mutex.synchronize do
109
169
  after = Marshal.load(copy.export)
110
170
  after.each_with_index do |m, i|
@@ -117,11 +177,31 @@ class Factbase
117
177
  end
118
178
 
119
179
  # Export it into a chain of bytes.
180
+ #
181
+ # Here is how you can export it to a file, for example:
182
+ #
183
+ # fb = Factbase.new
184
+ # fb.insert.foo = 42
185
+ # File.binwrite("foo.fb", fb.export)
186
+ #
187
+ # The data is binary, it's not a text!
188
+ #
189
+ # @return [Bytes] The chain of bytes
120
190
  def export
121
191
  Marshal.dump(@maps)
122
192
  end
123
193
 
124
194
  # Import from a chain of bytes.
195
+ #
196
+ # Here is how you can read it from a file, for example:
197
+ #
198
+ # fb = Factbase.new
199
+ # fb.import(File.binread("foo.fb"))
200
+ #
201
+ # The facts that existed in the factbase before importing will remain there.
202
+ # The facts from the incoming byte stream will added to them.
203
+ #
204
+ # @param [Bytes] bytes Byte array to import
125
205
  def import(bytes)
126
206
  @maps += Marshal.load(bytes)
127
207
  end
@@ -82,4 +82,25 @@ class TestTuples < Minitest::Test
82
82
  assert_equal(1, fb.query('(exists bar)').each.to_a.size)
83
83
  assert_equal(1, fb.query('(exists xyz)').each.to_a.size)
84
84
  end
85
+
86
+ def test_with_chaining
87
+ fb = Factbase.new
88
+ f1 = fb.insert
89
+ f1.name = 'Jeff'
90
+ f1.friend = 'Walter'
91
+ f2 = fb.insert
92
+ f2.name = 'Walter'
93
+ f2.group = 1
94
+ f3 = fb.insert
95
+ f3.name = 'Donny'
96
+ f3.group = 1
97
+ tuples = Factbase::Tuples.new(
98
+ fb, ['(eq name "Jeff")', '(eq name "{f0.friend}")', '(eq group {f1.group})']
99
+ )
100
+ tuples.each do |fs|
101
+ assert_equal('Walter', fs[1].name)
102
+ assert(%w[Walter Donny].include?(fs[2].name))
103
+ end
104
+ assert_equal(2, tuples.each.to_a.size)
105
+ end
85
106
  end
@@ -140,4 +140,13 @@ class TestFactbase < Minitest::Test
140
140
  end
141
141
  assert_equal(1, fb.query('(exists xyz)').each.to_a.size)
142
142
  end
143
+
144
+ def test_txn_with_rollback
145
+ fb = Factbase.new
146
+ fb.txn do |fbt|
147
+ fbt.insert.bar = 33
148
+ raise Factbase::Rollback
149
+ end
150
+ assert_equal(0, fb.query('(always)').each.to_a.size)
151
+ end
143
152
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.38
4
+ version: 0.0.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-27 00:00:00.000000000 Z
11
+ date: 2024-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json