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 +7 -0
- data/COPYING +22 -0
- data/README.md +22 -0
- data/Rakefile +68 -0
- data/lib/spinoza/common.rb +2 -0
- data/lib/spinoza/system/link.rb +0 -0
- data/lib/spinoza/system/lock-manager.rb +154 -0
- data/lib/spinoza/system/node.rb +17 -0
- data/lib/spinoza/system/operation.rb +88 -0
- data/lib/spinoza/system/store.rb +44 -0
- data/lib/spinoza/system/table-spec.rb +31 -0
- data/lib/spinoza/transaction.rb +56 -0
- data/lib/spinoza/version.rb +3 -0
- data/test/test-node.rb +87 -0
- data/test/test-transaction.rb +37 -0
- metadata +97 -0
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
|
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
|
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:
|