Skip to content

Commit 977e8bf

Browse files
committed
Merge pull request #56 from ruby-rdf/feature/graph-transaction
Feature/graph transaction
2 parents cc0c7da + 98fb7ec commit 977e8bf

3 files changed

Lines changed: 216 additions & 6 deletions

File tree

lib/rdf/spec/repository.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
require 'rdf/spec/dataset'
2424
it_behaves_like 'an RDF::Dataset'
2525
end
26+
27+
context 'as transactable' do
28+
require 'rdf/spec/transactable'
29+
let(:transactable) { repository }
30+
it_behaves_like 'an RDF::Transactable'
31+
end
2632

2733
context "when updating" do
2834
require 'rdf/spec/mutable'

lib/rdf/spec/transactable.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
require 'rdf/spec'
2+
3+
RSpec.shared_examples 'an RDF::Transactable' do
4+
include RDF::Spec::Matchers
5+
6+
let(:statements) { RDF::Spec.quads }
7+
8+
before do
9+
raise '`transactable` must be set with `let(:transactable)`' unless
10+
defined? transactable
11+
end
12+
13+
subject { transactable }
14+
15+
describe "#transaction" do
16+
it 'gives an immutable transaction' do
17+
expect { subject.transaction { insert([]) } }.to raise_error TypeError
18+
end
19+
20+
it 'commits a successful transaction' do
21+
statement = RDF::Statement(:s, RDF.type, :o)
22+
expect(subject).to receive(:commit_transaction).and_call_original
23+
24+
expect do
25+
subject.transaction(mutable: true) { insert(statement) }
26+
end.to change { subject.statements }.to include(statement)
27+
end
28+
29+
it 'rolls back a failed transaction' do
30+
original_contents = subject.statements
31+
expect(subject).to receive(:rollback_transaction).and_call_original
32+
33+
expect do
34+
subject.transaction(mutable: true) do
35+
delete(*@statements)
36+
raise 'my error'
37+
end
38+
end.to raise_error RuntimeError
39+
40+
expect(subject.statements).to contain_exactly(*original_contents)
41+
end
42+
end
43+
end

lib/rdf/spec/transaction.rb

Lines changed: 167 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Pass in an instance of RDF::Transaction as follows:
44
#
5-
# it_behaves_like "RDF::Transaction", RDF::Transaction
5+
# it_behaves_like "an RDF::Transaction", RDF::Transaction
66
shared_examples "an RDF::Transaction" do |klass|
77
include RDF::Spec::Matchers
88

@@ -40,6 +40,17 @@
4040
it 'allows mutability' do
4141
expect(klass.new(repository, mutable: true)).to be_mutable
4242
end
43+
44+
it 'accepts a graph_name' do
45+
graph_uri = RDF::URI('http://example.com/graph_1')
46+
47+
expect(klass.new(repository, graph_name: graph_uri).graph_name)
48+
.to eq graph_uri
49+
end
50+
51+
it 'defaults graph_name to nil' do
52+
expect(klass.new(repository).graph_name).to be_nil
53+
end
4354
end
4455

4556
it "does not respond to #load" do
@@ -68,7 +79,7 @@
6879
let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') }
6980

7081
it 'adds to deletes' do
71-
subject.repository.insert(st)
82+
repository.insert(st)
7283

7384
expect do
7485
subject.delete(st)
@@ -78,7 +89,7 @@
7889

7990
it 'adds multiple to deletes' do
8091
sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z')
81-
subject.repository.insert(*sts)
92+
repository.insert(*sts)
8293

8394
expect do
8495
subject.delete(*sts)
@@ -89,13 +100,55 @@
89100
it 'adds enumerable to deletes' do
90101
sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z')
91102
sts.extend(RDF::Enumerable)
92-
subject.repository.insert(sts)
103+
repository.insert(sts)
93104

94105
expect do
95106
subject.delete(sts)
96107
subject.execute
97108
end.to change { subject.repository.empty? }.from(false).to(true)
98109
end
110+
111+
context 'with a graph_name' do
112+
subject { klass.new(repository, mutable: true, graph_name: graph_uri) }
113+
114+
let(:graph_uri) { RDF::URI('http://example.com/graph_1') }
115+
116+
it 'adds the graph_name to statements' do
117+
repository.insert(st)
118+
with_name = st.dup
119+
with_name.graph_name = graph_uri
120+
repository.insert(with_name)
121+
122+
expect do
123+
subject.delete(st)
124+
subject.execute
125+
end.to change { subject.repository.statements }
126+
127+
expect(subject.repository).not_to have_statement(with_name)
128+
expect(subject.repository).to have_statement(st)
129+
end
130+
131+
it 'retains existing graph names' do
132+
st.graph_name = RDF::URI('g')
133+
repository.insert(st)
134+
135+
expect do
136+
subject.delete(st)
137+
subject.execute
138+
end.to change { subject.repository.statements }.to be_empty
139+
end
140+
141+
it 'retains existing default graph name' do
142+
st.graph_name = false
143+
144+
repository.insert(st)
145+
146+
expect do
147+
subject.delete(st)
148+
subject.execute
149+
end.to change { subject.repository.statements }.to be_empty
150+
end
151+
end
99152
end
100153

101154
describe "#insert" do
@@ -129,11 +182,51 @@
129182
end.to change { subject.repository.statements }
130183
.to contain_exactly(*sts)
131184
end
185+
186+
context 'with a graph_name' do
187+
subject { klass.new(repository, mutable: true, graph_name: graph_uri) }
188+
189+
let(:graph_uri) { RDF::URI('http://example.com/graph_1') }
190+
191+
it 'adds the graph_name to statements' do
192+
with_name = st.dup
193+
with_name.graph_name = graph_uri
194+
195+
expect do
196+
subject.insert(st)
197+
subject.execute
198+
end.to change { subject.repository }
199+
200+
expect(subject.repository).to have_statement(with_name)
201+
end
202+
203+
it 'retains existing graph names' do
204+
st.graph_name = RDF::URI('g')
205+
206+
expect do
207+
subject.insert(st)
208+
subject.execute
209+
end.to change { subject.repository.statements }
210+
211+
expect(subject.repository).to have_statement(st)
212+
end
213+
214+
it 'retains existing default graph name' do
215+
st.graph_name = false
216+
217+
expect do
218+
subject.insert(st)
219+
subject.execute
220+
end.to change { subject.repository.statements }
221+
222+
expect(subject.repository).to have_statement(st)
223+
end
224+
end
132225
end
133226

134227
describe '#execute' do
135-
# @todo: test isolation semantics!
136-
228+
let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') }
229+
137230
context 'after rollback' do
138231
before { subject.rollback }
139232

@@ -142,6 +235,74 @@
142235
.to raise_error RDF::Transaction::TransactionError
143236
end
144237
end
238+
239+
context 'when :read_committed' do
240+
it 'does not read uncommitted statements' do
241+
unless subject.isolation_level == :read_uncommitted
242+
read_tx = klass.new(repository, mutable: true)
243+
subject.insert(st)
244+
245+
expect(read_tx.statements).not_to include(st)
246+
end
247+
end
248+
249+
it 'reads committed statements' do
250+
if subject.isolation_level == :read_committed
251+
read_tx = klass.new(repository)
252+
subject.insert(st)
253+
subject.execute
254+
255+
expect(read_tx.statements).to include(st)
256+
end
257+
end
258+
end
259+
260+
context 'when :repeatable_read' do
261+
it 'does not read committed statements already read' do
262+
if subject.isolation_level == :repeatable_read ||
263+
subject.isolation_level == :snapshot ||
264+
subject.isolation_level == :serializable
265+
read_tx = klass.new(repository)
266+
subject.insert(st)
267+
subject.execute
268+
269+
expect(read_tx.statements).not_to include(st)
270+
end
271+
end
272+
end
273+
274+
context 'when :snapshot' do
275+
it 'does not read committed statements' do
276+
if subject.isolation_level == :snapshot ||
277+
subject.isolation_level == :serializable
278+
read_tx = klass.new(repository)
279+
subject.insert(st)
280+
subject.execute
281+
282+
expect(read_tx.statements).not_to include(st)
283+
end
284+
end
285+
286+
it 'reads current transaction state' do
287+
if subject.isolation_level == :snapshot ||
288+
subject.isolation_level == :serializable
289+
subject.insert(st)
290+
expect(subject.statements).to include(st)
291+
end
292+
end
293+
end
294+
295+
context 'when :serializable' do
296+
it 'raises an error if conflicting changes are present' do
297+
if subject.isolation_level == :serializable
298+
subject.insert(st)
299+
repository.insert(st)
300+
301+
expect { subject.execute }
302+
.to raise_error RDF::Transaction::TransactionError
303+
end
304+
end
305+
end
145306
end
146307

147308
describe '#rollback' do

0 commit comments

Comments
 (0)