|
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 |
|
|
40 | 40 | it 'allows mutability' do |
41 | 41 | expect(klass.new(repository, mutable: true)).to be_mutable |
42 | 42 | 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 |
43 | 54 | end |
44 | 55 |
|
45 | 56 | it "does not respond to #load" do |
|
68 | 79 | let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') } |
69 | 80 |
|
70 | 81 | it 'adds to deletes' do |
71 | | - subject.repository.insert(st) |
| 82 | + repository.insert(st) |
72 | 83 |
|
73 | 84 | expect do |
74 | 85 | subject.delete(st) |
|
78 | 89 |
|
79 | 90 | it 'adds multiple to deletes' do |
80 | 91 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') |
81 | | - subject.repository.insert(*sts) |
| 92 | + repository.insert(*sts) |
82 | 93 |
|
83 | 94 | expect do |
84 | 95 | subject.delete(*sts) |
|
89 | 100 | it 'adds enumerable to deletes' do |
90 | 101 | sts = [st] << RDF::Statement(:x, RDF::URI('y'), 'z') |
91 | 102 | sts.extend(RDF::Enumerable) |
92 | | - subject.repository.insert(sts) |
| 103 | + repository.insert(sts) |
93 | 104 |
|
94 | 105 | expect do |
95 | 106 | subject.delete(sts) |
96 | 107 | subject.execute |
97 | 108 | end.to change { subject.repository.empty? }.from(false).to(true) |
98 | 109 | 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 |
99 | 152 | end |
100 | 153 |
|
101 | 154 | describe "#insert" do |
|
129 | 182 | end.to change { subject.repository.statements } |
130 | 183 | .to contain_exactly(*sts) |
131 | 184 | 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 |
132 | 225 | end |
133 | 226 |
|
134 | 227 | describe '#execute' do |
135 | | - # @todo: test isolation semantics! |
136 | | - |
| 228 | + let(:st) { RDF::Statement(:s, RDF::URI('p'), 'o') } |
| 229 | + |
137 | 230 | context 'after rollback' do |
138 | 231 | before { subject.rollback } |
139 | 232 |
|
|
142 | 235 | .to raise_error RDF::Transaction::TransactionError |
143 | 236 | end |
144 | 237 | 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 |
145 | 306 | end |
146 | 307 |
|
147 | 308 | describe '#rollback' do |
|
0 commit comments