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