1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Dataquay
5 
6     A C++/Qt library for simple RDF datastore management.
7     Copyright 2009-2012 Chris Cannam.
8 
9     Permission is hereby granted, free of charge, to any person
10     obtaining a copy of this software and associated documentation
11     files (the "Software"), to deal in the Software without
12     restriction, including without limitation the rights to use, copy,
13     modify, merge, publish, distribute, sublicense, and/or sell copies
14     of the Software, and to permit persons to whom the Software is
15     furnished to do so, subject to the following conditions:
16 
17     The above copyright notice and this permission notice shall be
18     included in all copies or substantial portions of the Software.
19 
20     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
24     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
25     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 
28     Except as contained in this notice, the name of Chris Cannam
29     shall not be used in advertising or otherwise to promote the sale,
30     use or other dealings in this Software without prior written
31     authorization.
32 */
33 
34 #ifndef _TEST_TRANSACTIONAL_STORE_H_
35 #define _TEST_TRANSACTIONAL_STORE_H_
36 
37 #include <dataquay/Node.h>
38 #include <dataquay/BasicStore.h>
39 #include <dataquay/RDFException.h>
40 #include <dataquay/TransactionalStore.h>
41 #include <dataquay/Connection.h>
42 
43 #include <QObject>
44 #include <QtTest>
45 
46 namespace Dataquay {
47 
48 class TestTransactionalStore : public QObject
49 {
50     Q_OBJECT
51 
52 private slots:
initTestCase()53     void initTestCase() {
54 	store.setBaseUri(Uri("http://breakfastquay.com/rdf/dataquay/tests#"));
55 	ts = new TransactionalStore(&store);
56     }
57 
init()58     void init() {
59 	store.clear();
60     }
61 
simpleAdds()62     void simpleAdds() {
63 	Transaction *t = ts->startTransaction();
64 	int added = 0;
65 	QVERIFY(addThings(t, added));
66 
67 	// pause to test transactional isolation
68 	Triples triples = ts->match(Triple());
69 	QCOMPARE(triples.size(), 0);
70 
71 	// do it again, just to check internal state isn't being bungled
72 	triples = ts->match(Triple());
73 	QCOMPARE(triples.size(), 0);
74 
75 	// and check that reads *through* the transaction return the
76 	// partial state as expected
77 	triples = t->match(Triple());
78 	QCOMPARE(triples.size(), added);
79 
80 	t->commit();
81 	triples = ts->match(Triple());
82 	QCOMPARE(triples.size(), added);
83 
84 	// this is the bogus value we added and then removed in addThings
85 	QVERIFY(!ts->contains(Triple(store.expand(":fred"),
86                                      store.expand(":age"),
87                                      Node::fromVariant(QVariant(43)))));
88 
89 	// this is the value we actually retained
90 	QVERIFY(ts->contains(Triple(store.expand(":fred"),
91                                     store.expand(":age"),
92                                     Node::fromVariant(QVariant(42)))));
93 
94 	delete t;
95     }
96 
simpleRollback()97     void simpleRollback() {
98 
99 	Transaction *t = ts->startTransaction();
100 	int added = 0;
101 	QVERIFY(addThings(t, added));
102 
103 	t->rollback();
104 	delete t;
105 
106 	Triples triples = ts->match(Triple());
107 	QCOMPARE(triples.size(), 0);
108     }
109 
autoRollback()110     void autoRollback() {
111 	// automatic rollback triggered by an exception in e.g. add
112 
113 	Transaction *t = ts->startTransaction();
114 	int added = 0;
115 	QVERIFY(addThings(t, added));
116 
117 	// Add incomplete statement to provoke an exception
118 	try {
119 	    t->add(Triple(Node(),
120 			  Uri("http://xmlns.com/foaf/0.1/name"),
121 			  Node("this_statement_is_incomplete")));
122 	    QVERIFY(0);
123 	} catch (const RDFException &) {
124 	    QVERIFY(1);
125 	}
126 
127 	// Now everything should fail on this tx
128 	try {
129 	    t->add(Triple(store.expand(":fred2"),
130 			  store.expand(":is_sadly_deluded"),
131 			  Node::fromVariant(true)));
132 	    QVERIFY2(0, "add succeeded after auto-rollback, should have failed");
133 	} catch (const RDFException &) {
134 	    QVERIFY(1);
135 	}
136 
137 	try {
138 	    (void)t->match(Triple());
139 	    QVERIFY2(0, "match succeeded after auto-rollback, should have failed");
140 	} catch (const RDFException &) {
141 	    QVERIFY(1);
142 	}
143 
144 	// including commit
145 	try {
146 	    t->commit();
147 	    QVERIFY2(0, "commit succeeded after auto-rollback, should have failed");
148 	} catch (const RDFException &) {
149 	    QVERIFY(1);
150 	}
151 
152 	// and rollback!
153 	try {
154 	    t->rollback();
155 	    QVERIFY2(0, "rollback succeed after auto-rollback, should have failed");
156 	} catch (const RDFException &) {
157 	    QVERIFY(1);
158 	}
159 
160 	delete t;
161     }
162 
emptyTx()163     void emptyTx() {
164 	Transaction *t = ts->startTransaction();
165 	t->commit();
166 	delete t;
167 	t = ts->startTransaction();
168 	t->rollback();
169 	delete t;
170     }
171 
mustCommitOrRollback()172     void mustCommitOrRollback() {
173 	Transaction *t = ts->startTransaction();
174 	int added = 0;
175 	QVERIFY(addThings(t, added));
176 
177         // Logic has changed here -- we no longer throw from the
178         // dtor. Instead it just recovers, but a warning message is
179         // printed.
180         delete t;
181 
182 	// and check that this doesn't prevent any further
183 	// transactions from happening
184 	t = ts->startTransaction();
185 	t->rollback();
186 	delete t;
187     }
188 
noConcurrentTxInThread()189     void noConcurrentTxInThread() {
190 	Transaction *t = ts->startTransaction();
191 	try {
192 	    Transaction *tt = ts->startTransaction();
193 	    QVERIFY(0);
194             tt->rollback();
195 	} catch (const RDFException &) {
196 	    QVERIFY(1);
197 	}
198 	t->rollback();
199 	delete t;
200     }
201 
consecutiveTxInThread()202     void consecutiveTxInThread() {
203 	Transaction *t = ts->startTransaction();
204 	t->commit();
205 	delete t;
206 	Transaction *tt = ts->startTransaction();
207 	tt->commit();
208 	delete tt;
209     }
210 
cannotUseFinishedTx()211     void cannotUseFinishedTx() {
212 	Transaction *t = ts->startTransaction();
213 	int added = 0;
214 	QVERIFY(addThings(t, added));
215 
216 	// Test that we can't use the transaction after a rollback
217 	t->rollback();
218 	try {
219 	    QVERIFY(!t->add(Triple(store.expand(":fred2"),
220 				   Uri("http://xmlns.com/foaf/0.1/knows"),
221 				   store.expand(":samuel"))));
222 	    QVERIFY(0);
223 	} catch (const RDFException &) {
224 	    QVERIFY(1);
225 	}
226 
227 	delete t;
228 	t = ts->startTransaction();
229 	QVERIFY(addThings(t, added));
230 
231 	//... or a commit
232 	t->commit();
233 	try {
234 	    Triples triples = t->match(Triple());
235 	    QVERIFY(0);
236 	} catch (const RDFException &) {
237 	    QVERIFY(1);
238 	}
239 
240 	// and that we can't commit or rollback twice
241 	delete t;
242 	t = ts->startTransaction();
243 	t->commit();
244 	try {
245 	    t->commit();
246 	    QVERIFY(0);
247 	} catch (const RDFException &) {
248 	    QVERIFY(1);
249 	}
250 	delete t;
251 	t = ts->startTransaction();
252 	t->rollback();
253 	try {
254 	    t->rollback();
255 	    QVERIFY(0);
256 	} catch (const RDFException &) {
257 	    QVERIFY(1);
258 	}
259 	delete t;
260     }
261 
changesets()262     void changesets() {
263 	Transaction *t = ts->startTransaction();
264 	int added = 0;
265 	QVERIFY(addThings(t, added));
266 
267 	ChangeSet changes = t->getChanges();
268 	QVERIFY(!changes.empty());
269 
270 	ChangeSet cchanges = t->getCommittedChanges();
271 	QVERIFY(cchanges.empty());
272 
273 	t->commit();
274 
275 	cchanges = t->getCommittedChanges();
276 	QCOMPARE(cchanges, changes);
277 
278 	t = ts->startTransaction();
279 	t->revert(changes);
280 	t->commit();
281 	delete t;
282 
283 	Triples triples = ts->match(Triple());
284 	QCOMPARE(triples.size(), 0);
285 
286 	t = ts->startTransaction();
287 	t->change(changes);
288 	t->commit();
289 	delete t;
290 
291 	triples = ts->match(Triple());
292 	QCOMPARE(triples.size(), added);
293 
294 	// this is the bogus value we added and then removed in addThings
295 	QVERIFY(!ts->contains(Triple(store.expand(":fred"),
296 				     store.expand(":age"),
297 				     Node::fromVariant(QVariant(43)))));
298 
299 	// this is the value we actually retained
300 	QVERIFY(ts->contains(Triple(store.expand(":fred"),
301 				    store.expand(":age"),
302 				    Node::fromVariant(QVariant(42)))));
303     }
304 
simpleConnection()305     void simpleConnection() {
306 
307 	Connection *c = new Connection(ts);
308 	int added = 0;
309 	QVERIFY(addThings(c, added));
310 
311         // query on connection
312         Triples triples = c->match(Triple());
313         QCOMPARE(triples.size(), added);
314 
315         // query on store
316         triples = ts->match(Triple());
317         QCOMPARE(triples.size(), 0);
318 
319         // query on a different connection
320         {
321             Connection c2(ts);
322             triples = c2.match(Triple());
323 	    QCOMPARE(triples.size(), 0);
324         }
325 
326         c->commit();
327 
328         triples = c->match(Triple());
329         QCOMPARE(triples.size(), added);
330 
331         triples = ts->match(Triple());
332         QCOMPARE(triples.size(), added);
333 
334         c->add(Triple(store.expand(":fred"),
335                      store.expand(":likes_to_think_his_age_is"),
336                      Node::fromVariant(QVariant(21.9))));
337 
338         ++added;
339 
340         triples = c->match(Triple());
341         QCOMPARE(triples.size(), added);
342 
343         triples = ts->match(Triple());
344         QCOMPARE(triples.size(), added-1);
345 
346         c->commit();
347 
348         triples = c->match(Triple());
349         QCOMPARE(triples.size(), added);
350 
351         triples = ts->match(Triple());
352         QCOMPARE(triples.size(), added);
353 
354         // test implicit commit on dtor
355         for (int i = 0; i < triples.size(); ++i) {
356             c->remove(triples[i]);
357         }
358 
359 	delete c;
360 
361         triples = ts->match(Triple());
362         QCOMPARE(triples.size(), 0);
363     }
364 
365 private:
366     BasicStore store;
367     TransactionalStore *ts;
368 
addThings(Store * t,int & added)369     bool addThings(Store *t, int &added) {
370 	added = 0;
371 	// These add calls are things we've tested in testBasicStore already
372 	if (!t->add(Triple(store.expand(":fred"),
373 			   Uri("http://xmlns.com/foaf/0.1/name"),
374 			   Node("Fred Jenkins")))) return false;
375 	++added;
376 	if (!t->add(Triple(store.expand(":fred"),
377 			   Uri("http://xmlns.com/foaf/0.1/knows"),
378 			   store.expand(":alice")))) return false;
379 	++added;
380 	t->add(Triple(store.expand(":fred"),
381 		      store.expand(":age"),
382 		      Node::fromVariant(QVariant(43))));
383 	++added;
384 	if (!t->remove(Triple(store.expand(":fred"),
385 			      store.expand(":age"),
386 			      Node()))) return false;
387 	--added;
388 	if (!t->add(Triple(store.expand(":fred"),
389 			   store.expand(":age"),
390 			   Node::fromVariant(QVariant(42))))) return false;
391 	++added;
392 	return true;
393     }
394 };
395 
396 }
397 
398 #endif
399