rdf-spec 1.99.0 → 2.0.0.beta1

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.
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