spinoza 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 868a9f9ccabcf28a96ace9484c9c0caf0bbacd26
4
+ data.tar.gz: 42d844a448adc075556b5933731bcb90e4a58356
5
+ SHA512:
6
+ metadata.gz: 4c178a4b1cf1e14cf538dec9602c4a25eca2f57a003a8f8db2b2b92fb9b55feb9014979db38caabc6af9ce2f7fc26dfda73b564da454599fb8b054f80132585b
7
+ data.tar.gz: ae5bcbdf391a45950d1d3aa4efc0ac52f10d99d413f0ee96e639c179ecb054a311ae14ab3448d0ecbc22976162e02e22d71a0cbb4088e091aa193decb7a1c86a
data/COPYING ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2014, Joel VanderWerf
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ spinoza
2
+ =======
3
+
4
+ A model of the Calvin distributed database.
5
+
6
+ Spinoza, like Calvin, was a philosopher who dealt in determinism.
7
+
8
+ The model of the underlying computer and network system is in lib/spinoza/system.
9
+
10
+ Calvin is developed by the Yale Databases group; the open-source releases are at https://github.com/yaledb.
11
+
12
+ Contact
13
+ =======
14
+
15
+ Joel VanderWerf, vjoel@users.sourceforge.net, [@JoelVanderWerf](https://twitter.com/JoelVanderWerf).
16
+
17
+ License and Copyright
18
+ ========
19
+
20
+ Copyright (c) 2014, Joel VanderWerf
21
+
22
+ License for this project is BSD. See the COPYING file for the standard BSD license.
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ PRJ = "spinoza"
5
+
6
+ def version
7
+ @version ||= begin
8
+ require 'spinoza/version'
9
+ warn "Spinoza::VERSION not a string" unless Spinoza::VERSION.kind_of? String
10
+ Spinoza::VERSION
11
+ end
12
+ end
13
+
14
+ def tag
15
+ @tag ||= "#{PRJ}-#{version}"
16
+ end
17
+
18
+ desc "Run all tests"
19
+ task :test => %w{ test:unit }
20
+
21
+ namespace :test do
22
+ desc "Run unit tests"
23
+ Rake::TestTask.new :unit do |t|
24
+ t.libs << "lib"
25
+ t.test_files = FileList["test/*.rb"]
26
+ end
27
+ end
28
+
29
+ desc "Commit, tag, and push repo; build and push gem"
30
+ task :release => "release:is_new_version" do
31
+ require 'tempfile'
32
+
33
+ sh "gem build #{PRJ}.gemspec"
34
+
35
+ file = Tempfile.new "template"
36
+ begin
37
+ file.puts "release #{version}"
38
+ file.close
39
+ sh "git commit --allow-empty -a -v -t #{file.path}"
40
+ ensure
41
+ file.close unless file.closed?
42
+ file.unlink
43
+ end
44
+
45
+ sh "git tag #{tag}"
46
+ sh "git push"
47
+ sh "git push --tags"
48
+
49
+ sh "gem push #{tag}.gem"
50
+ end
51
+
52
+ namespace :release do
53
+ desc "Diff to latest release"
54
+ task :diff do
55
+ latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
56
+ sh "git diff #{latest}"
57
+ end
58
+
59
+ desc "Log to latest release"
60
+ task :log do
61
+ latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
62
+ sh "git log #{latest}.."
63
+ end
64
+
65
+ task :is_new_version do
66
+ abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
67
+ end
68
+ end
@@ -0,0 +1,2 @@
1
+ module Spinoza
2
+ end
File without changes
@@ -0,0 +1,154 @@
1
+ require 'spinoza/common'
2
+
3
+ # Manages concurrency in the spinoza system model, which explicitly schedules
4
+ # all database reads and writes. So all this does is check for concurrency
5
+ # violations; nothing actually blocks.
6
+ class Spinoza::LockManager
7
+ class ConcurrencyError < StandardError; end
8
+
9
+ # State of lock on one resource (e.g., a row), which may be held concurrently
10
+ # by any number of threads. Reentrant.
11
+ class ReadLock
12
+ attr_reader :txns
13
+
14
+ def initialize txn
15
+ @txns = Hash.new(0)
16
+ add txn
17
+ end
18
+
19
+ def unlocked?
20
+ @txns.all? {|t,c| c == 0}
21
+ end
22
+
23
+ def includes? txn
24
+ @txns[txn] > 0
25
+ end
26
+
27
+ def add txn
28
+ @txns[txn] += 1
29
+ end
30
+
31
+ def remove txn
32
+ if includes? txn
33
+ @txns[txn] -= 1
34
+ @txns.delete txn if @txns[txn] == 0
35
+ else
36
+ raise ConcurrencyError, "#{self} is not locked by #{txn}"
37
+ end
38
+ end
39
+ end
40
+
41
+ # State of lock on one resource (e.g., a row), which may be held concurrently
42
+ # by at most one thread. Reentrant.
43
+ class WriteLock
44
+ attr_reader :txn, :count
45
+
46
+ def initialize txn
47
+ @txn = txn
48
+ @count = 1
49
+ end
50
+
51
+ def unlocked?
52
+ @txn == nil
53
+ end
54
+
55
+ def includes? txn
56
+ @txn == txn && @count > 0
57
+ end
58
+
59
+ def add txn
60
+ if includes? txn
61
+ @count += 1
62
+ else
63
+ raise ConcurrencyError, "#{self} is not locked by #{txn}"
64
+ end
65
+ end
66
+
67
+ def remove txn
68
+ if includes? txn
69
+ @count -= 1
70
+ @txn = nil if @count == 0
71
+ else
72
+ raise ConcurrencyError, "#{self} is not locked by #{txn}"
73
+ end
74
+ end
75
+ end
76
+
77
+ # { resource => WriteLock | ReadLock | nil, ... }
78
+ # typically, resource == [table, key]
79
+ attr_reader :locks
80
+
81
+ def initialize
82
+ @locks = {}
83
+ end
84
+
85
+ def lock_read resource, txn
86
+ case lock = locks[resource]
87
+ when nil
88
+ locks[resource] = ReadLock.new(txn)
89
+ when ReadLock
90
+ lock.add txn
91
+ when WriteLock
92
+ raise ConcurrencyError, "#{resource} is locked: #{lock}"
93
+ else raise
94
+ end
95
+ end
96
+
97
+ def lock_write resource, txn
98
+ case lock = locks[resource]
99
+ when nil
100
+ locks[resource] = WriteLock.new(txn)
101
+ when WriteLock
102
+ lock.add txn
103
+ when ReadLock
104
+ raise ConcurrencyError, "#{resource} is locked: #{lock}"
105
+ else raise
106
+ end
107
+ end
108
+
109
+ def unlock_read resource, txn
110
+ lock = locks[resource]
111
+ case lock
112
+ when WriteLock
113
+ raise ConcurrencyError, "#{resource} is write locked: #{lock}"
114
+ when nil
115
+ raise ConcurrencyError, "#{resource} is not locked"
116
+ when ReadLock
117
+ begin
118
+ lock.remove txn
119
+ locks.delete resource if lock.unlocked?
120
+ rescue ConcurrencyError => ex
121
+ raise ConcurrencyError "#{resource}: #{ex.message}"
122
+ end
123
+ else raise
124
+ end
125
+ end
126
+
127
+ def unlock_write resource, txn
128
+ lock = locks[resource]
129
+ case lock
130
+ when ReadLock
131
+ raise ConcurrencyError, "#{resource} is read locked: #{lock}"
132
+ when nil
133
+ raise ConcurrencyError, "#{resource} is not locked"
134
+ when WriteLock
135
+ begin
136
+ lock.remove txn
137
+ locks.delete resource if lock.unlocked?
138
+ rescue ConcurrencyError => ex
139
+ raise ConcurrencyError "#{resource}: #{ex.message}"
140
+ end
141
+ else raise
142
+ end
143
+ end
144
+
145
+ def has_read_lock? resource, txn
146
+ lock = locks[resource]
147
+ lock.kind_of?(ReadLock) && lock.includes?(txn)
148
+ end
149
+
150
+ def has_write_lock? resource, txn
151
+ lock = locks[resource]
152
+ lock.kind_of?(WriteLock) && lock.includes?(txn)
153
+ end
154
+ end
@@ -0,0 +1,17 @@
1
+ require 'spinoza/system/store'
2
+ require 'spinoza/system/table-spec'
3
+ require 'spinoza/system/lock-manager'
4
+
5
+ # A top level entity in the system model, representing on whole node of
6
+ # the disrtibuted system, typically one per host.
7
+ class Spinoza::Node
8
+ attr_reader :store
9
+ attr_reader :lock_manager
10
+
11
+ # Create a node whose store contains the specified tables and which has
12
+ # its own lock manager.
13
+ def initialize *table_specs
14
+ @store = Store.new *table_specs
15
+ @lock_manager = LockManager.new
16
+ end
17
+ end
@@ -0,0 +1,88 @@
1
+ require 'spinoza/common'
2
+
3
+ module Spinoza
4
+ # Operations are stateless and do not reference any particular store. Hence
5
+ # they can be serialized and passed around the system.
6
+ class Operation
7
+ # Name of table.
8
+ attr_reader :table
9
+
10
+ # Transaction this operation belongs to.
11
+
12
+ # ds is Sequel dataset representing the table
13
+ def execute ds
14
+ raise
15
+ end
16
+
17
+ # check that this operation is allowed to execute
18
+ # lm is LockManager
19
+ def check lm
20
+ raise
21
+ end
22
+ end
23
+
24
+ class InsertOperation < Operation
25
+ attr_reader :row
26
+ def initialize txn = nil, table: table, row: row
27
+ @txn = txn
28
+ @table, @row = table, row
29
+ end
30
+
31
+ def execute ds
32
+ ds << row
33
+ end
34
+
35
+ def check lm
36
+ true
37
+ end
38
+ end
39
+
40
+ class UpdateOperation < Operation
41
+ attr_reader :key, :row
42
+ def initialize txn = nil, table: table, row: row, key: key
43
+ @txn = txn
44
+ @table, @key, @row = table, key, row
45
+ end
46
+
47
+ def execute ds
48
+ ds.where(key).update(row)
49
+ end
50
+
51
+ def check lm
52
+ lm.has_write_lock? table, key, txn
53
+ end
54
+ end
55
+
56
+ class DeleteOperation < Operation
57
+ attr_reader :key
58
+ def initialize txn = nil, table: table, key: key
59
+ @txn = txn
60
+ @table, @key = table, key
61
+ end
62
+
63
+ def execute ds
64
+ ds.where(key).delete
65
+ end
66
+ end
67
+
68
+ class ReadOperation < Operation
69
+ attr_reader :key
70
+ def initialize txn = nil, table: table, key: key
71
+ @txn = txn
72
+ @table, @key = table, key
73
+ end
74
+
75
+ def execute ds
76
+ ReadResult.new(op: self, val: ds.where(key).all)
77
+ end
78
+ end
79
+
80
+ # Result of executing a read operation on a particular node at a
81
+ # particular time.
82
+ class ReadResult
83
+ attr_reader :op, :val
84
+ def initialize op: op, val: val
85
+ @op, @val = op, val
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,44 @@
1
+ require 'sequel'
2
+ require 'spinoza/system/operation'
3
+
4
+ # Represents the storage at one node. In our model, this consists of an
5
+ # in-memory sqlite database with some very simple tables.
6
+ class Spinoza::Store
7
+ def initialize *table_specs
8
+ @db = Sequel.sqlite
9
+ @table_specs = table_specs
10
+
11
+ table_specs.each do |table_spec|
12
+ @db.create_table table_spec.name do
13
+ table_spec.column_specs.each do |col|
14
+ case col.type
15
+ when "integer", "string", "float"
16
+ column col.name, col.type, primary_key: col.primary
17
+ else
18
+ raise ArgumentError,
19
+ "Bad col.type: #{col.type} in table #{table_spec.name}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def tables
27
+ @db.tables
28
+ end
29
+
30
+ def inspect
31
+ "<#{self.class.name}: #{tables.join(', ')}>"
32
+ end
33
+
34
+ # Execute the operations on this store, skipping any that do not refer to
35
+ # a table in this store.
36
+ def execute *operations
37
+ results = operations.map do |op|
38
+ if tables.include? op.table
39
+ op.execute @db[op.table]
40
+ end
41
+ end
42
+ results.grep(ReadResult)
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ require 'spinoza/common'
2
+
3
+ # Defines the schema for one table in all replicas.
4
+ class Spinoza::TableSpec
5
+ attr_reader :name
6
+ attr_reader :column_specs
7
+
8
+ class ColumnSpec
9
+ attr_reader :name
10
+ attr_reader :type
11
+ attr_reader :primary
12
+
13
+ def initialize name, type, primary
14
+ @name, @type, @primary = name, type, primary
15
+ end
16
+ end
17
+
18
+ # Usage:
19
+ #
20
+ # create_table :Widgets, id: "integer", weight: "float", name: "string"
21
+ #
22
+ # The first column is the primary key for the table.
23
+ #
24
+ def initialize name, **specs
25
+ @name = name
26
+ @column_specs = []
27
+ specs.each_with_index do |(col_name, col_type), i|
28
+ @column_specs << ColumnSpec.new(col_name, col_type, (i==0))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ require 'spinoza/system/operation'
2
+
3
+ class Spinoza::Transaction
4
+ attr_reader :ops
5
+
6
+ class RowLocation
7
+ def initialize txn, table, key
8
+ @txn, @table, @key = txn, table, key
9
+ end
10
+
11
+ def insert row
12
+ @txn.ops << InsertOperation.new(@txn, table: @table, row: row)
13
+ end
14
+
15
+ def update row
16
+ @txn.ops << UpdateOperation.new(@txn, table: @table, row: row, key: @key)
17
+ end
18
+
19
+ def delete
20
+ @txn.ops << DeleteOperation.new(@txn, table: @table, key: @key)
21
+ end
22
+
23
+ def read
24
+ @txn.ops << ReadOperation.new(@txn, table: @table, key: @key)
25
+ end
26
+ end
27
+
28
+ # A txn is just a list of operations, each on a specific table and key.
29
+ # The ACID guarantees are not provided by this class.
30
+ #
31
+ # Example:
32
+ #
33
+ # txn = transaction do
34
+ # at(:persons, name: "Fred").update(age: 41, phrase: "yabba dabba doo")
35
+ # at(:persons, name: "Wilma").delete
36
+ # at(:persons, name: "Barney").read
37
+ # at(:persons).insert(name: "Betty", age: 65)
38
+ # end
39
+ #
40
+ # node.store.execute txn # ==> [ReadResult(...)]
41
+ #
42
+ def initialize &block
43
+ @ops = []
44
+ if block
45
+ if block.arity == 0
46
+ instance_eval &block
47
+ else
48
+ yield self
49
+ end
50
+ end
51
+ end
52
+
53
+ def at table, **key
54
+ RowLocation.new(self, table, key)
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module Spinoza
2
+ VERSION = "0.1"
3
+ end
data/test/test-node.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/system/node'
3
+
4
+ include Spinoza
5
+
6
+ class TestNode < Minitest::Test
7
+ def setup
8
+ @node = Node.new(
9
+ TableSpec.new :foos, id: "integer", name: "string", len: "float"
10
+ )
11
+ end
12
+
13
+ def test_store
14
+ assert_equal [:foos], @node.store.tables
15
+ end
16
+
17
+ def test_ops
18
+ store = @node.store
19
+
20
+ op_i = InsertOperation.new(table: :foos, row: {id: 1, name: "a", len: 1.2})
21
+ op_r = ReadOperation.new(table: :foos, key: {id: 1})
22
+ rslt = store.execute op_i, op_r
23
+
24
+ assert_equal(1, rslt.size)
25
+ r_tuples = rslt[0].val
26
+ assert_equal(1, r_tuples.size)
27
+ assert_equal({id: 1, name: "a", len: 1.2}, r_tuples[0])
28
+
29
+ op_u = UpdateOperation.new(table: :foos, key: {id: 1}, row: {name: "b"})
30
+ rslt = store.execute op_u, op_r
31
+
32
+ assert_equal(1, rslt.size)
33
+ r_tuples = rslt[0].val
34
+ assert_equal(1, r_tuples.size)
35
+ assert_equal({id: 1, name: "b", len: 1.2}, r_tuples[0])
36
+
37
+ op_d = DeleteOperation.new(table: :foos, key: {id: 1})
38
+ rslt = store.execute op_d, op_r
39
+ assert_equal(1, rslt.size)
40
+ r_tuples = rslt[0].val
41
+ assert_equal(0, r_tuples.size)
42
+ end
43
+
44
+ def test_locks
45
+ lm = @node.lock_manager
46
+
47
+ rs1 = [:foos, 1]
48
+ rs2 = [:foos, 2]
49
+
50
+ lm.lock_read rs1, :t1
51
+ assert lm.has_read_lock?(rs1, :t1)
52
+ refute lm.has_read_lock?(rs1, :t2)
53
+
54
+ lm.lock_read rs1, :t2
55
+ assert lm.has_read_lock?(rs1, :t1)
56
+ assert lm.has_read_lock?(rs1, :t2)
57
+
58
+ lm.unlock_read rs1, :t1
59
+ refute lm.has_read_lock?(rs1, :t1)
60
+ assert lm.has_read_lock?(rs1, :t2)
61
+
62
+ assert_raises LockManager::ConcurrencyError do
63
+ lm.lock_write rs1, :t1
64
+ end
65
+
66
+ lm.lock_read rs1, :t2
67
+ assert lm.has_read_lock?(rs1, :t2)
68
+ lm.unlock_read rs1, :t2
69
+ assert lm.has_read_lock?(rs1, :t2)
70
+ lm.unlock_read rs1, :t2
71
+ refute lm.has_read_lock?(rs1, :t2)
72
+
73
+ lm.lock_write rs2, :t1
74
+ assert lm.has_write_lock?(rs2, :t1)
75
+ assert_raises LockManager::ConcurrencyError do
76
+ lm.lock_read rs2, :t2
77
+ end
78
+ assert lm.has_write_lock?(rs2, :t1)
79
+
80
+ lm.lock_write rs2, :t1
81
+ assert lm.has_write_lock?(rs2, :t1)
82
+ lm.unlock_write rs2, :t1
83
+ assert lm.has_write_lock?(rs2, :t1)
84
+ lm.unlock_write rs2, :t1
85
+ refute lm.has_write_lock?(rs2, :t1)
86
+ end
87
+ end
@@ -0,0 +1,37 @@
1
+ require 'minitest/autorun'
2
+ require 'spinoza/system/node'
3
+ require 'spinoza/transaction'
4
+
5
+ include Spinoza
6
+
7
+ class TestTransaction < Minitest::Test
8
+ def setup
9
+ @node = Node.new(
10
+ TableSpec.new :foos, id: "integer", name: "string", len: "float"
11
+ )
12
+ end
13
+
14
+ def test_txn
15
+ txn = Transaction.new do
16
+ at(:foos).insert id: 1, name: "a", len: 1.2
17
+ at(:foos).insert id: 2, name: "b", len: 3.4
18
+ at(:foos, id: 2).read
19
+ at(:foos).insert id: 3, name: "c", len: 5.6
20
+ at(:foos, id: 2).delete
21
+ at(:foos, id: 2).read
22
+ at(:foos, id: 3).update len: 7.8
23
+ at(:foos, id: 3).read
24
+ end
25
+
26
+ rslt = @node.store.execute(*txn.ops)
27
+ assert_equal(3, rslt.size)
28
+
29
+ assert_equal(1, rslt[0].val.size)
30
+ assert_equal({id: 2, name: "b", len: 3.4}, rslt[0].val[0])
31
+
32
+ assert_equal(0, rslt[1].val.size)
33
+
34
+ assert_equal(1, rslt[2].val.size)
35
+ assert_equal({id: 3, name: "c", len: 7.8}, rslt[2].val[0])
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spinoza
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Joel VanderWerf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Model of Calvin distributed database.
42
+ email: vjoel@users.sourceforge.net
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files:
46
+ - README.md
47
+ - COPYING
48
+ files:
49
+ - COPYING
50
+ - README.md
51
+ - Rakefile
52
+ - lib/spinoza/common.rb
53
+ - lib/spinoza/system/link.rb
54
+ - lib/spinoza/system/lock-manager.rb
55
+ - lib/spinoza/system/node.rb
56
+ - lib/spinoza/system/operation.rb
57
+ - lib/spinoza/system/store.rb
58
+ - lib/spinoza/system/table-spec.rb
59
+ - lib/spinoza/transaction.rb
60
+ - lib/spinoza/version.rb
61
+ - test/test-node.rb
62
+ - test/test-transaction.rb
63
+ homepage: https://github.com/vjoel/spinoza
64
+ licenses:
65
+ - BSD
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options:
69
+ - "--quiet"
70
+ - "--line-numbers"
71
+ - "--inline-source"
72
+ - "--title"
73
+ - spinoza
74
+ - "--main"
75
+ - README.md
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.2.2
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Model of Calvin distributed database.
94
+ test_files:
95
+ - test/test-node.rb
96
+ - test/test-transaction.rb
97
+ has_rdoc: