spinoza 0.1

Sign up to get free protection for your applications and to get access to all the features.
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: