rdf-spec 1.99.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/{README → README.md} +2 -0
  3. data/VERSION +1 -1
  4. data/lib/rdf/spec.rb +18 -0
  5. data/lib/rdf/spec/countable.rb +5 -28
  6. data/lib/rdf/spec/dataset.rb +47 -0
  7. data/lib/rdf/spec/durable.rb +8 -23
  8. data/lib/rdf/spec/enumerable.rb +118 -102
  9. data/lib/rdf/spec/format.rb +4 -26
  10. data/lib/rdf/spec/http_adapter.rb +1 -23
  11. data/lib/rdf/spec/indexable.rb +1 -23
  12. data/lib/rdf/spec/inferable.rb +0 -16
  13. data/lib/rdf/spec/inspects.rb +4 -5
  14. data/lib/rdf/spec/matchers.rb +95 -4
  15. data/lib/rdf/spec/mutable.rb +227 -81
  16. data/lib/rdf/spec/queryable.rb +122 -165
  17. data/lib/rdf/spec/readable.rb +0 -22
  18. data/lib/rdf/spec/reader.rb +21 -29
  19. data/lib/rdf/spec/repository.rb +80 -40
  20. data/lib/rdf/spec/transactable.rb +43 -0
  21. data/lib/rdf/spec/transaction.rb +294 -71
  22. data/lib/rdf/spec/version.rb +2 -2
  23. data/lib/rdf/spec/writable.rb +78 -100
  24. data/lib/rdf/spec/writer.rb +51 -28
  25. data/spec/countable_spec.rb +11 -0
  26. data/spec/dataset_spec.rb +14 -0
  27. data/spec/durable_spec.rb +12 -0
  28. data/spec/enumerable_spec.rb +11 -0
  29. data/spec/format_spec.rb +12 -0
  30. data/spec/http_adapter_spec.rb +15 -0
  31. data/spec/indexable.rb +15 -0
  32. data/spec/literal_spec.rb +75 -0
  33. data/spec/mutable_spec.rb +11 -0
  34. data/spec/queryable_spec.rb +13 -0
  35. data/spec/readable.rb +11 -0
  36. data/spec/reader_spec.rb +17 -0
  37. data/spec/repository_spec.rb +11 -0
  38. data/spec/spec_helper.rb +13 -0
  39. data/spec/transaction_spec.rb +7 -0
  40. data/spec/version_spec.rb +2 -0
  41. data/spec/writable_spec.rb +13 -0
  42. data/spec/writer_spec.rb +11 -0
  43. metadata +56 -12
@@ -15,65 +15,105 @@ RSpec.shared_examples 'an RDF::Repository' do
15
15
  end
16
16
  end
17
17
 
18
- let(:countable) { repository }
19
- let(:enumerable) { repository }
20
- let(:queryable) { repository }
21
18
  let(:mutable) { repository }
19
+ let(:dataset) { repository.supports?(:snapshots) ? repository.snapshot : repository }
20
+ subject { repository }
22
21
 
23
- context "when counting statements" do
24
- require 'rdf/spec/countable'
25
- it_behaves_like 'an RDF::Countable'
22
+ context 'as dataset' do
23
+ require 'rdf/spec/dataset'
24
+ it_behaves_like 'an RDF::Dataset'
26
25
  end
27
26
 
28
- context "when enumerating statements" do
29
- require 'rdf/spec/enumerable'
30
- it_behaves_like 'an RDF::Enumerable'
27
+ context 'as transactable' do
28
+ require 'rdf/spec/transactable'
29
+ let(:transactable) { repository }
30
+ it_behaves_like 'an RDF::Transactable'
31
31
  end
32
-
33
- context "when querying statements" do
34
- require 'rdf/spec/queryable'
35
- it_behaves_like 'an RDF::Queryable'
36
- end
37
-
38
- # FIXME: This should be condition on the repository being mutable
32
+
39
33
  context "when updating" do
40
34
  require 'rdf/spec/mutable'
41
35
 
42
- before { mutable.clear }
43
-
36
+ before { mutable.clear if mutable.mutable? }
44
37
  it_behaves_like 'an RDF::Mutable'
38
+
39
+ describe '#delete_insert' do
40
+ it 'updates transactionally' do
41
+ if mutable.mutable? && mutable.supports?(:atomic_writes)
42
+ expect(mutable).to receive(:commit_transaction).and_call_original
43
+ statement = RDF::Statement(:s, RDF::URI.new("urn:predicate:1"), :o)
44
+
45
+ mutable.delete_insert([statement], [statement])
46
+ end
47
+ end
48
+ end
45
49
  end
46
50
 
47
- # FIXME: This should be condition on the repository being mutable
48
- context "as a durable repository" do
49
- require 'rdf/spec/durable'
51
+ describe "#transaction" do
52
+ it 'gives an immutable transaction' do
53
+ expect { subject.transaction { insert([]) } }.to raise_error TypeError
54
+ end
50
55
 
51
- before :each do
52
- repository.clear
53
- @load_durable ||= lambda { repository }
56
+ it 'commits a successful transaction' do
57
+ statement = RDF::Statement(:s, RDF.type, :o)
58
+ expect(subject).to receive(:commit_transaction).and_call_original
59
+
60
+ expect do
61
+ subject.transaction(mutable: true) { insert(statement) }
62
+ end.to change { subject.statements }.to include(statement)
54
63
  end
55
64
 
56
- it_behaves_like 'an RDF::Durable'
57
- end
58
- end
65
+ it 'rolls back a failed transaction' do
66
+ original_contents = subject.statements
67
+ expect(subject).to receive(:rollback_transaction).and_call_original
59
68
 
60
- ##
61
- # @deprecated use `it_behaves_like "an RDF::Repository"` instead
62
- module RDF_Repository
63
- extend RSpec::SharedContext
64
- include RDF::Spec::Matchers
69
+ expect do
70
+ subject.transaction(mutable: true) do
71
+ delete(*@statements)
72
+ raise 'my error'
73
+ end
74
+ end.to raise_error RuntimeError
65
75
 
66
- def self.included(mod)
67
- warn "[DEPRECATION] `RDF_Repository` is deprecated. "\
68
- "Please use `it_behaves_like 'an RDF::Repository'`"
76
+ expect(subject.statements).to contain_exactly(*original_contents)
77
+ end
69
78
  end
70
79
 
71
- describe 'examples for' do
72
- include_examples 'an RDF::Repository' do
73
- let(:repository) { @repository }
80
+ context "with snapshot support" do
81
+
82
+ describe '#snapshot' do
83
+ it 'is not implemented when #supports(:snapshots) is false' do
84
+ unless subject.supports?(:snapshots)
85
+ expect { subject.snapshot }.to raise_error NotImplementedError
86
+ end
87
+ end
88
+ end
89
+
90
+ it 'returns a queryable #snapshot' do
91
+ if subject.supports? :snapshots
92
+ expect(subject.snapshot).to be_a RDF::Queryable
93
+ expect(mutable.snapshot).to be_a RDF::Dataset
94
+ end
95
+ end
96
+
97
+ it 'has repeatable read isolation or better' do
98
+ if repository.supports? :snapshots
99
+ good_isolation = [:repeatable_read, :snapshot, :serializable]
100
+ expect(good_isolation).to include repository.isolation_level
101
+ end
102
+ end
103
+
104
+ it 'gives an accurate snapshot' do
105
+ if subject.supports? :snapshots
106
+ snap = subject.snapshot
107
+ expect(snap.query([:s, :p, :o]))
108
+ .to contain_exactly(*subject.query([:s, :p, :o]))
109
+ end
110
+ end
74
111
 
75
- before do
76
- raise '@repository must be defined' unless defined?(repository)
112
+ it 'gives static snapshot' do
113
+ if subject.supports? :snapshots
114
+ snap = subject.snapshot
115
+ expect { subject.clear }
116
+ .not_to change { snap.query([:s, :p, :o]).to_a }
77
117
  end
78
118
  end
79
119
  end
@@ -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
@@ -2,118 +2,341 @@ require 'rdf/spec'
2
2
 
3
3
  # Pass in an instance of RDF::Transaction as follows:
4
4
  #
5
- # it_behaves_like "RDF::Transaction", RDF::Transaction
5
+ # it_behaves_like "an RDF::Transaction", RDF::Transaction
6
6
  shared_examples "an RDF::Transaction" do |klass|
7
7
  include RDF::Spec::Matchers
8
8
 
9
- subject {klass.new(graph_name: RDF::URI("name"), inser: RDF::Graph.new, delete: RDF::Graph.new)}
9
+ before do
10
+ raise 'repository must be set with `let(:repository)' unless
11
+ defined? repository
12
+ end
10
13
 
11
- describe "#initialize" do
12
- subject {klass}
13
- it "accepts a graph" do
14
- g = double("graph")
15
- this = subject.new(graph: g)
16
- expect(this.graph).to eq g
14
+ subject { klass.new(repository, mutable: true) }
15
+
16
+ it { is_expected.to be_readable }
17
+ it { is_expected.to be_queryable }
18
+
19
+ context "when querying statements" do
20
+ require 'rdf/spec/queryable'
21
+ let(:queryable) do
22
+ repository.insert(*RDF::Spec.quads)
23
+ klass.new(repository)
24
+ end
25
+ it_behaves_like 'an RDF::Queryable'
26
+
27
+ context 'with a graph_name' do
28
+ let(:queryable) do
29
+ graph_name = RDF::URI('http://example.com/g')
30
+ graph = RDF::Graph.new(graph_name: graph_name, data: repository)
31
+ graph.insert(*RDF::Spec.quads)
32
+ klass.new(repository, graph_name: graph_name)
33
+ end
34
+ it_behaves_like 'an RDF::Queryable'
35
+ end
36
+
37
+ context 'with a false graph_name' do
38
+ let(:queryable) do
39
+ graph = RDF::Graph.new(data: repository)
40
+ graph.insert(*RDF::Spec.quads)
41
+ graph_name = false
42
+ klass.new(repository, graph_name: graph_name)
43
+ end
44
+ it_behaves_like 'an RDF::Queryable'
17
45
  end
46
+ end
18
47
 
19
- it "accepts a context", unless: RDF::VERSION.to_s >= "1.99" do
20
- c = double("context")
21
- this = subject.new(graph: c)
22
- expect(this.graph).to eq c
23
- expect(this.context).to eq c
48
+ describe "#initialize" do
49
+ it 'accepts a repository' do
50
+ repo = double('repository')
51
+ allow(repo).to receive_messages(:supports? => false)
24
52
 
25
- this = subject.new(context: c)
26
- expect(this.graph).to eq c
27
- expect(this.context).to eq c
53
+ expect(klass.new(repo).repository).to eq repo
28
54
  end
29
55
 
30
- it "accepts a graph_name", if: RDF::VERSION.to_s >= "1.99" do
31
- c = double("graph_name")
32
- this = subject.new(graph: c)
33
- expect(this.graph).to eq c
34
- expect(this.graph_name).to eq c
56
+ it 'defaults immutable (read only)' do
57
+ expect(klass.new(repository).mutable?).to be false
58
+ end
35
59
 
36
- this = subject.new(graph_name: c)
37
- expect(this.graph).to eq c
38
- expect(this.graph_name).to eq c
60
+ it 'allows mutability' do
61
+ expect(klass.new(repository, mutable: true)).to be_mutable
39
62
  end
40
63
 
41
- it "accepts inserts" do
42
- g = double("inserts")
43
- this = subject.new(insert: g)
44
- expect(this.inserts).to eq g
64
+ it 'accepts a graph_name' do
65
+ graph_uri = RDF::URI('http://example.com/graph_1')
66
+
67
+ expect(klass.new(repository, graph_name: graph_uri).graph_name)
68
+ .to eq graph_uri
45
69
  end
46
70
 
47
- it "accepts deletes" do
48
- g = double("deletes")
49
- this = subject.new(delete: g)
50
- expect(this.deletes).to eq g
71
+ it 'defaults graph_name to nil' do
72
+ expect(klass.new(repository).graph_name).to be_nil
51
73
  end
52
74
  end
53
75
 
54
- its(:deletes) {is_expected.to be_a(RDF::Enumerable)}
55
- its(:inserts) {is_expected.to be_a(RDF::Enumerable)}
56
- it {is_expected.to be_mutable}
57
- it {is_expected.to_not be_readable}
58
-
59
76
  it "does not respond to #load" do
60
- expect {subject.load("http://example/")}.to raise_error(NoMethodError)
77
+ expect { subject.load("http://example/") }.to raise_error(NoMethodError)
61
78
  end
62
79
 
63
80
  it "does not respond to #update" do
64
- expect {subject.update(RDF::Statement.new)}.to raise_error(NoMethodError)
81
+ expect { subject.update(RDF::Statement.new) }.to raise_error(NoMethodError)
65
82
  end
66
83
 
67
84
  it "does not respond to #clear" do
68
- expect {subject.clear}.to raise_error(NoMethodError)
85
+ expect { subject.clear }.to raise_error(NoMethodError)
69
86
  end
70
87
 
71
- describe "#execute" do
72
- let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))}
73
- let(:r) {double("repository")}
88
+ describe '#changes' do
89
+ it 'is a changeset' do
90
+ expect(subject.changes).to be_a RDF::Changeset
91
+ end
74
92
 
75
- it "deletes statements" do
76
- expect(r).to receive(:delete).with(s)
77
- expect(r).not_to receive(:insert)
78
- subject.delete(s)
79
- subject.execute(r)
93
+ it 'is initially empty' do
94
+ expect(subject.changes).to be_empty
80
95
  end
96
+ end
81
97
 
82
- it "inserts statements" do
83
- expect(r).not_to receive(:delete)
84
- expect(r).to receive(:insert).with(s)
85
- subject.insert(s)
86
- subject.execute(r)
98
+ describe "#delete" do
99
+ let(:st) { RDF::Statement(:s, RDF::URI('http://example.com/p'), 'o') }
100
+
101
+ it 'adds to deletes' do
102
+ repository.insert(st)
103
+
104
+ expect do
105
+ subject.delete(st)
106
+ subject.execute
107
+ end.to change { subject.repository.empty? }.from(false).to(true)
87
108
  end
88
109
 
89
- it "calls before_execute" do
90
- is_expected.to receive(:before_execute).with(r, {})
91
- subject.execute(r)
110
+ it 'adds multiple to deletes' do
111
+ sts = [st] << RDF::Statement(:x, RDF::URI('http://example.com/y'), 'z')
112
+ repository.insert(*sts)
113
+
114
+ expect do
115
+ subject.delete(*sts)
116
+ subject.execute
117
+ end.to change { subject.repository.empty? }.from(false).to(true)
92
118
  end
93
119
 
94
- it "calls after_execute" do
95
- is_expected.to receive(:after_execute).with(r, {})
96
- subject.execute(r)
120
+ it 'adds enumerable to deletes' do
121
+ sts = [st] << RDF::Statement(:x, RDF::URI('http://example.com/y'), 'z')
122
+ sts.extend(RDF::Enumerable)
123
+ repository.insert(sts)
124
+
125
+ expect do
126
+ subject.delete(sts)
127
+ subject.execute
128
+ end.to change { subject.repository.empty? }.from(false).to(true)
97
129
  end
98
130
 
99
- it "returns self" do
100
- expect(subject.execute(r)).to eq subject
131
+ context 'with a graph_name' do
132
+ subject { klass.new(repository, mutable: true, graph_name: graph_uri) }
133
+
134
+ let(:graph_uri) { RDF::URI('http://example.com/graph_1') }
135
+
136
+ it 'adds the graph_name to statements' do
137
+ repository.insert(st)
138
+ with_name = st.dup
139
+ with_name.graph_name = graph_uri
140
+ repository.insert(with_name)
141
+
142
+ expect do
143
+ subject.delete(st)
144
+ subject.execute
145
+ end.to change { subject.repository.statements }
146
+
147
+ expect(subject.repository).not_to have_statement(with_name)
148
+ expect(subject.repository).to have_statement(st)
149
+ end
150
+
151
+ it 'overwrites existing graph names' do
152
+ st.graph_name = RDF::URI('http://example.com/g')
153
+ repository.insert(st)
154
+
155
+ expect do
156
+ subject.delete(st)
157
+ subject.execute
158
+ end.not_to change { subject.repository.statements }
159
+ end
160
+
161
+ it 'overwrites existing default graph name' do
162
+ st.graph_name = false
163
+
164
+ repository.insert(st)
165
+
166
+ expect do
167
+ subject.delete(st)
168
+ subject.execute
169
+ end.not_to change { subject.repository.statements }
170
+ end
101
171
  end
102
172
  end
103
173
 
104
- describe "#delete_statement" do
105
- let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))}
106
- it "adds statement to #deletes" do
107
- subject.delete(s)
108
- expect(subject.deletes.to_a).to eq [s]
174
+ describe "#insert" do
175
+ let(:st) { RDF::Statement(:s, RDF::URI('http://example.com/p'), 'o') }
176
+
177
+ it 'adds to inserts' do
178
+ expect do
179
+ subject.insert(st)
180
+ subject.execute
181
+ end.to change { subject.repository.statements }
182
+ .to contain_exactly(st)
183
+ end
184
+
185
+ it 'adds multiple to inserts' do
186
+ sts = [st] << RDF::Statement(:x, RDF::URI('http://example.com/y'), 'z')
187
+
188
+ expect do
189
+ subject.insert(*sts)
190
+ subject.execute
191
+ end.to change { subject.repository.statements }
192
+ .to contain_exactly(*sts)
193
+ end
194
+
195
+ it 'adds enumerable to inserts' do
196
+ sts = [st] << RDF::Statement(:x, RDF::URI('http://example.com/y'), 'z')
197
+ sts.extend(RDF::Enumerable)
198
+
199
+ expect do
200
+ subject.insert(sts)
201
+ subject.execute
202
+ end.to change { subject.repository.statements }
203
+ .to contain_exactly(*sts)
204
+ end
205
+
206
+ context 'with a graph_name' do
207
+ subject { klass.new(repository, mutable: true, graph_name: graph_uri) }
208
+
209
+ let(:graph_uri) { RDF::URI('http://example.com/graph_1') }
210
+
211
+ it 'adds the graph_name to statements' do
212
+ with_name = st.dup
213
+ with_name.graph_name = graph_uri
214
+
215
+ expect do
216
+ subject.insert(st)
217
+ subject.execute
218
+ end.to change { subject.repository }
219
+
220
+ expect(subject.repository).to have_statement(with_name)
221
+ end
222
+
223
+ it 'overwrites existing graph names' do
224
+ st.graph_name = RDF::URI('http://example.com/g')
225
+ with_name = st.dup
226
+ with_name.graph_name = graph_uri
227
+
228
+ expect do
229
+ subject.insert(st)
230
+ subject.execute
231
+ end.to change { subject.repository.statements }
232
+
233
+ expect(subject.repository).not_to have_statement(st)
234
+ expect(subject.repository).to have_statement(with_name)
235
+ end
236
+
237
+ it 'overwrites existing default graph name' do
238
+ st.graph_name = false
239
+ with_name = st.dup
240
+ with_name.graph_name = graph_uri
241
+
242
+ expect do
243
+ subject.insert(st)
244
+ subject.execute
245
+ end.to change { subject.repository.statements }
246
+
247
+ expect(subject.repository).not_to have_statement(st)
248
+ expect(subject.repository).to have_statement(with_name)
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '#execute' do
254
+ let(:st) { RDF::Statement(:s, RDF::URI('http://example.com/p'), 'o') }
255
+
256
+ context 'after rollback' do
257
+ before { subject.rollback }
258
+
259
+ it 'does not execute' do
260
+ expect { subject.execute }
261
+ .to raise_error RDF::Transaction::TransactionError
262
+ end
263
+ end
264
+
265
+ context 'when :read_committed' do
266
+ it 'does not read uncommitted statements' do
267
+ unless subject.isolation_level == :read_uncommitted
268
+ read_tx = klass.new(repository, mutable: true)
269
+ subject.insert(st)
270
+
271
+ expect(read_tx.statements).not_to include(st)
272
+ end
273
+ end
274
+
275
+ it 'reads committed statements' do
276
+ if subject.isolation_level == :read_committed
277
+ read_tx = klass.new(repository)
278
+ subject.insert(st)
279
+ subject.execute
280
+
281
+ expect(read_tx.statements).to include(st)
282
+ end
283
+ end
284
+ end
285
+
286
+ context 'when :repeatable_read' do
287
+ it 'does not read committed statements already read' do
288
+ if subject.isolation_level == :repeatable_read ||
289
+ subject.isolation_level == :snapshot ||
290
+ subject.isolation_level == :serializable
291
+ read_tx = klass.new(repository)
292
+ subject.insert(st)
293
+ subject.execute
294
+
295
+ expect(read_tx.statements).not_to include(st)
296
+ end
297
+ end
298
+ end
299
+
300
+ context 'when :snapshot' do
301
+ it 'does not read committed statements' do
302
+ if subject.isolation_level == :snapshot ||
303
+ subject.isolation_level == :serializable
304
+ read_tx = klass.new(repository)
305
+ subject.insert(st)
306
+ subject.execute
307
+
308
+ expect(read_tx.statements).not_to include(st)
309
+ end
310
+ end
311
+
312
+ it 'reads current transaction state' do
313
+ if subject.isolation_level == :snapshot ||
314
+ subject.isolation_level == :serializable
315
+ subject.insert(st)
316
+ expect(subject.statements).to include(st)
317
+ end
318
+ end
319
+ end
320
+
321
+ context 'when :serializable' do
322
+ it 'raises an error if conflicting changes are present' do
323
+ if subject.isolation_level == :serializable
324
+ subject.insert(st)
325
+ repository.insert(st)
326
+
327
+ expect { subject.execute }
328
+ .to raise_error RDF::Transaction::TransactionError
329
+ end
330
+ end
109
331
  end
110
332
  end
111
333
 
112
- describe "#insert_statement" do
113
- let(:s) {RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o"))}
114
- it "adds statement to #inserts" do
115
- subject.insert(s)
116
- expect(subject.inserts.to_a).to eq [s]
334
+ describe '#rollback' do
335
+ before { subject.insert(st); subject.delete(st) }
336
+ let(:st) { RDF::Statement(:s, RDF::URI('http://example.com/p'), 'o') }
337
+
338
+ it 'empties changes when available' do
339
+ expect { subject.rollback }.to change { subject.changes }.to be_empty
117
340
  end
118
341
  end
119
342
  end