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 #ifdef USE_SORD
35
36 #include "BasicStore.h"
37 #include "RDFException.h"
38
39 #include <sord/sord.h>
40
41 #include <QThread>
42 #include <QMutex>
43 #include <QMutexLocker>
44 #include <QHash>
45 #include <QFile>
46 #include <QCryptographicHash>
47 #include <QReadWriteLock>
48
49 #include "../Debug.h"
50
51 #include <cstdlib>
52 #include <iostream>
53 #include <time.h>
54
55 namespace Dataquay
56 {
57
58 class BasicStore::D
59 {
60 public:
D()61 D() : m_model(0) {
62 m_prefixes["rdf"] = Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
63 m_prefixes["xsd"] = Uri("http://www.w3.org/2001/XMLSchema#");
64 clear();
65 }
66
~D()67 ~D() {
68 QMutexLocker locker(&m_backendLock);
69 if (m_model) sord_free(m_model);
70 }
71
getNewString() const72 QString getNewString() const {
73 QString s =
74 QString::fromLocal8Bit
75 (QCryptographicHash::hash
76 (QString("%1").arg(rand() + time(0)).toLocal8Bit(),
77 QCryptographicHash::Sha1).toHex())
78 .left(12);
79 // This may be used as the whole of a name in some contexts,
80 // so it must not start with a digit
81 if (s[0].isDigit()) {
82 s = "x" + s.right(s.length()-1);
83 }
84 return s;
85 }
86
collision() const87 void collision() const {
88 // If we get a collision when generating a "random" string,
89 // seed the random number generator (it probably means the
90 // generator hasn't been seeded at all). But only once.
91 static QMutex m;
92 static bool seeded = false;
93 static QMutexLocker l(&m);
94 if (!seeded) return;
95 srand((unsigned int)time(0));
96 seeded = true;
97 }
98
setBaseUri(Uri baseUri)99 void setBaseUri(Uri baseUri) {
100 QMutexLocker plocker(&m_prefixLock);
101 m_baseUri = baseUri;
102 m_prefixes[""] = m_baseUri;
103 }
104
getBaseUri() const105 Uri getBaseUri() const {
106 return m_baseUri;
107 }
108
clear()109 void clear() {
110 QMutexLocker locker(&m_backendLock);
111 DQ_DEBUG << "BasicStore::clear" << endl;
112 if (m_model) sord_free(m_model);
113 // Sord can only perform wildcard matches if at least one of
114 // the non-wildcard nodes in the matched triple is the primary
115 // term for one of its indices
116 m_model = sord_new(m_w.getWorld(), SORD_SPO|SORD_OPS|SORD_POS, false);
117 if (!m_model) throw RDFInternalError("Failed to create RDF data model");
118 }
119
addPrefix(QString prefix,Uri uri)120 void addPrefix(QString prefix, Uri uri) {
121 QMutexLocker plocker(&m_prefixLock);
122 m_prefixes[prefix] = uri;
123 }
124
add(Triple t)125 bool add(Triple t) {
126 QMutexLocker locker(&m_backendLock);
127 DQ_DEBUG << "BasicStore::add: " << t << endl;
128 return doAdd(t);
129 }
130
remove(Triple t)131 bool remove(Triple t) {
132 QMutexLocker locker(&m_backendLock);
133 DQ_DEBUG << "BasicStore::remove: " << t << endl;
134 if (t.a.type == Node::Nothing ||
135 t.b.type == Node::Nothing ||
136 t.c.type == Node::Nothing) {
137 Triples tt = doMatch(t);
138 if (tt.empty()) return false;
139 DQ_DEBUG << "BasicStore::remove: Removing " << tt.size() << " triple(s)" << endl;
140 for (int i = 0; i < tt.size(); ++i) {
141 if (!doRemove(tt[i])) {
142 DQ_DEBUG << "Failed to remove matched triple in remove() with wildcards; triple was: " << tt[i] << endl;
143 throw RDFInternalError("Failed to remove matched statement in remove() with wildcards");
144 }
145 }
146 return true;
147 } else {
148 return doRemove(t);
149 }
150 }
151
change(ChangeSet cs)152 void change(ChangeSet cs) {
153 QMutexLocker locker(&m_backendLock);
154 DQ_DEBUG << "BasicStore::change: " << cs.size() << " changes" << endl;
155 for (int i = 0; i < cs.size(); ++i) {
156 ChangeType type = cs[i].first;
157 Triple triple = cs[i].second;
158 switch (type) {
159 case AddTriple:
160 if (!doAdd(triple)) {
161 throw RDFException("Change add failed: triple is already in store", triple);
162 }
163 break;
164 case RemoveTriple:
165 if (!doRemove(cs[i].second)) {
166 throw RDFException("Change remove failed: triple is not in store", triple);
167 }
168 break;
169 }
170 }
171 }
172
revert(ChangeSet cs)173 void revert(ChangeSet cs) {
174 QMutexLocker locker(&m_backendLock);
175 DQ_DEBUG << "BasicStore::revert: " << cs.size() << " changes" << endl;
176 for (int i = cs.size()-1; i >= 0; --i) {
177 ChangeType type = cs[i].first;
178 Triple triple = cs[i].second;
179 switch (type) {
180 case AddTriple:
181 if (!doRemove(triple)) {
182 throw RDFException("Revert of add failed: triple is not in store", triple);
183 }
184 break;
185 case RemoveTriple:
186 if (!doAdd(triple)) {
187 throw RDFException("Revert of remove failed: triple is already in store", triple);
188 }
189 break;
190 }
191 }
192 }
193
contains(Triple t) const194 bool contains(Triple t) const {
195 QMutexLocker locker(&m_backendLock);
196 DQ_DEBUG << "BasicStore::contains: " << t << endl;
197 SordQuad statement;
198 tripleToStatement(t, statement);
199 if (!checkComplete(statement)) {
200 throw RDFException("Failed to test for triple (statement is incomplete)");
201 }
202 if (!sord_contains(m_model, statement)) {
203 return false;
204 }
205 freeStatement(statement);
206 return true;
207 }
208
match(Triple t) const209 Triples match(Triple t) const {
210 QMutexLocker locker(&m_backendLock);
211 DQ_DEBUG << "BasicStore::match: " << t << endl;
212 Triples result = doMatch(t);
213 #ifndef NDEBUG
214 DQ_DEBUG << "BasicStore::match result (size " << result.size() << "):" << endl;
215 for (int i = 0; i < result.size(); ++i) {
216 DQ_DEBUG << i << ". " << result[i] << endl;
217 }
218 #endif
219 return result;
220 }
221
complete(Triple t) const222 Node complete(Triple t) const {
223 int count = 0, match = 0;
224 if (t.a == Node()) { ++count; match = 0; }
225 if (t.b == Node()) { ++count; match = 1; }
226 if (t.c == Node()) { ++count; match = 2; }
227 if (count != 1) {
228 throw RDFException("Cannot complete triple unless it has only a single wildcard node", t);
229 }
230 QMutexLocker locker(&m_backendLock);
231 DQ_DEBUG << "BasicStore::complete: " << t << endl;
232 Triples result = doMatch(t, true);
233 if (result.empty()) return Node();
234 else switch (match) {
235 case 0: return result[0].a;
236 case 1: return result[0].b;
237 case 2: return result[0].c;
238 default: return Node();
239 }
240 }
241
matchOnce(Triple t) const242 Triple matchOnce(Triple t) const {
243 if (t.c != Node() && t.b != Node() && t.a != Node()) {
244 // triple is complete: short-circuit to a single lookup
245 if (contains(t)) return t;
246 else return Triple();
247 }
248 QMutexLocker locker(&m_backendLock);
249 DQ_DEBUG << "BasicStore::matchOnce: " << t << endl;
250 Triples result = doMatch(t, true);
251 #ifndef NDEBUG
252 DQ_DEBUG << "BasicStore::matchOnce result:" << endl;
253 for (int i = 0; i < result.size(); ++i) {
254 DQ_DEBUG << i << ". " << result[i] << endl;
255 }
256 #endif
257 if (result.empty()) return Triple();
258 else return result[0];
259 }
260
query(QString sparql) const261 ResultSet query(QString sparql) const {
262 throw RDFUnsupportedError
263 ("SPARQL queries are not supported with Sord backend",
264 sparql);
265 }
266
queryOnce(QString sparql,QString) const267 Node queryOnce(QString sparql, QString /* bindingName */) const {
268 throw RDFUnsupportedError
269 ("SPARQL queries are not supported with Sord backend",
270 sparql);
271 }
272
getUniqueUri(QString prefix) const273 Uri getUniqueUri(QString prefix) const {
274 QMutexLocker locker(&m_backendLock);
275 DQ_DEBUG << "BasicStore::getUniqueUri: prefix " << prefix << endl;
276 bool good = false;
277 Uri uri;
278 while (!good) {
279 QString s = getNewString();
280 uri = expand(prefix + s);
281 Triples t = doMatch(Triple(uri, Node(), Node()), true);
282 if (t.empty()) good = true;
283 else collision();
284 }
285 return uri;
286 }
287
expand(QString shrt) const288 Uri expand(QString shrt) const {
289
290 if (shrt == "a") {
291 return Uri::rdfTypeUri();
292 }
293
294 int index = shrt.indexOf(':');
295 if (index == 0) {
296 // starts with colon
297 return Uri(m_baseUri.toString() + shrt.right(shrt.length() - 1));
298 } else if (index > 0) {
299 // colon appears in middle somewhere
300 if (index + 2 < shrt.length() &&
301 shrt[index+1] == '/' &&
302 shrt[index+2] == '/') {
303 // we have found "://", this is a scheme, therefore
304 // the uri is already expanded
305 return Uri(shrt);
306 }
307 } else {
308 // no colon present, no possibility of expansion
309 return Uri(shrt);
310 }
311
312 // fall through only for colon in middle and no "://" found,
313 // i.e. a plausible prefix appears
314
315 QString prefix = shrt.left(index);
316 QString expanded;
317
318 m_prefixLock.lock();
319 PrefixMap::const_iterator pi = m_prefixes.find(prefix);
320 if (pi != m_prefixes.end()) {
321 expanded = pi.value().toString() +
322 shrt.right(shrt.length() - (index + 1));
323 } else {
324 expanded = shrt;
325 }
326 m_prefixLock.unlock();
327
328 return Uri(expanded);
329 }
330
addBlankNode()331 Node addBlankNode() {
332 QMutexLocker locker(&m_backendLock);
333 QString blankId = getNewString();
334 //!!! todo: how to check whether the blank node is already in use
335 SordNode *node = sord_new_blank(m_w.getWorld(), (uint8_t *)blankId.toUtf8().data());
336 if (!node) throw RDFInternalError("Failed to create new blank node");
337 return sordNodeToNode(node);
338 }
339
saveSink(const void * buf,size_t len,void * stream)340 static size_t saveSink(const void *buf, size_t len, void *stream) {
341 QIODevice *dev = (QIODevice *)stream;
342 qint64 r = dev->write((const char *)buf, len);
343 if (r < 0) throw RDFException("Write failed");
344 else return r;
345 }
346
save(QString filename) const347 void save(QString filename) const {
348
349 QMutexLocker wlocker(&m_backendLock);
350 QMutexLocker plocker(&m_prefixLock);
351
352 DQ_DEBUG << "BasicStore::save(" << filename << ")" << endl;
353
354 QByteArray bb = m_baseUri.toString().toUtf8();
355 SerdURI bu;
356
357 if (serd_uri_parse((uint8_t *)bb.data(), &bu) != SERD_SUCCESS) {
358 throw RDFInternalError("Failed to parse base URI", m_baseUri);
359 }
360
361 SerdNode bn = serd_node_from_string(SERD_URI, (uint8_t *)bb.data());
362 SerdEnv *env = serd_env_new(&bn);
363
364 for (PrefixMap::const_iterator i = m_prefixes.begin();
365 i != m_prefixes.end(); ++i) {
366 addToSerdNamespace(env, i.key(), i.value().toString());
367 }
368
369 // addToSerdNamespace(env, QString(), m_baseUri.toString());
370
371 QFile f(filename);
372 if (!f.exists()) {
373 if (!f.open(QFile::WriteOnly)) {
374 throw RDFException("Failed to open file for writing", filename);
375 }
376 f.close();
377 }
378
379 QString tmpFilename = QString("%1.part").arg(filename);
380
381 QFile tf(tmpFilename);
382 if (!tf.open(QFile::WriteOnly)) {
383 throw RDFException("Failed to open partial file for writing", tmpFilename);
384 }
385
386 SerdEnv *wenv = serd_env_new(&bn);
387
388 SerdWriter *writer = serd_writer_new
389 (SERD_TURTLE,
390 SerdStyle(SERD_STYLE_ABBREVIATED | SERD_STYLE_RESOLVED | SERD_STYLE_CURIED),
391 wenv, &bu, saveSink, &tf);
392
393 serd_env_foreach(env,
394 (SerdPrefixSink)serd_writer_set_prefix,
395 writer);
396
397 sord_write(m_model, writer, NULL);
398
399 serd_writer_finish(writer);
400 serd_writer_free(writer);
401
402 serd_env_free(env);
403 serd_env_free(wenv);
404
405 tf.close();
406
407 // New file is now completed; the following is scruffy, but
408 // that shouldn't really matter now
409
410 if (!QFile::remove(filename)) {
411 // Not necessarily fatal
412 DQ_DEBUG << "BasicStore::save: Failed to remove former save file "
413 << filename << endl;
414 }
415 if (!QFile::rename(tmpFilename, filename)) {
416 throw RDFException("Failed to rename temporary file to save file",
417 filename);
418 }
419 }
420
addPrefixOnImport(QString pfx,Uri uri)421 void addPrefixOnImport(QString pfx, Uri uri) {
422
423 DQ_DEBUG << "namespace: " << pfx << " -> " << uri << endl;
424
425 // don't call addPrefix; it tries to lock the mutex,
426 // and anyway we want to add the prefix only if it
427 // isn't already there (to avoid surprisingly changing
428 // a prefix in unusual cases, or changing the base URI)
429 if (m_prefixes.find(pfx) == m_prefixes.end()) {
430 m_prefixes[pfx] = uri;
431 }
432 }
433
addPrefixSink(void * handle,const SerdNode * name,const SerdNode * uri)434 static SerdStatus addPrefixSink(void *handle,
435 const SerdNode *name,
436 const SerdNode *uri) {
437
438 D *d = (D *)handle;
439
440 try {
441
442 QString qpfx(QString::fromUtf8((const char *)name->buf,
443 (int)name->n_bytes));
444 Uri quri(QString::fromUtf8((const char *)uri->buf,
445 (int)uri->n_bytes));
446
447 d->addPrefixOnImport(qpfx, quri);
448
449 } catch (const RDFIncompleteURI &) {
450 }
451
452 return SERD_SUCCESS;
453 }
454
import(QUrl url,ImportDuplicatesMode idm,QString)455 void import(QUrl url, ImportDuplicatesMode idm, QString /* format */) {
456
457 DQ_DEBUG << "BasicStoreSord::import: " << url << endl;
458
459 QMutexLocker wlocker(&m_backendLock);
460 QMutexLocker plocker(&m_prefixLock);
461
462 //!!! todo: format?
463
464 QString base = m_baseUri.toString();
465 if (base == "") {
466 // No base URI in store: use file URL as base
467 base = url.toString();
468 }
469
470 QByteArray bb = base.toUtf8();
471 SerdURI bu;
472
473 if (serd_uri_parse((uint8_t *)bb.data(), &bu) != SERD_SUCCESS) {
474 throw RDFInternalError("Failed to parse base URI", base);
475 }
476
477 SerdNode bn = serd_node_from_string(SERD_URI, (uint8_t *)bb.data());
478 SerdEnv *env = serd_env_new(&bn);
479
480 QString fileUri = url.toString();
481
482 // serd_uri_to_path doesn't like the brief file:blah
483 // convention, it insists that file: is followed by //
484 // (the opposite of Redland)
485
486 if (fileUri.startsWith("file:") && !fileUri.startsWith("file://")) {
487 // however, it's happy with scheme-less paths
488 fileUri = fileUri.right(fileUri.length()-5);
489 }
490
491 if (idm == ImportPermitDuplicates) {
492
493 // No special handling for duplicates, do whatever the
494 // underlying engine does
495
496 SerdReader *reader = sord_new_reader(m_model, env, SERD_TURTLE, NULL);
497
498 // if we have data in the store already, then we must add
499 // a prefix for the new blank nodes we're importing to
500 // disambiguate them
501 if (!doMatch(Triple(), true).empty()) {
502 serd_reader_add_blank_prefix
503 (reader, (uint8_t *)(getNewString().toUtf8().data()));
504 }
505
506 SerdStatus rv = serd_reader_read_file
507 (reader, (const uint8_t *)fileUri.toLocal8Bit().data());
508
509 if (rv != SERD_SUCCESS) {
510 serd_reader_free(reader);
511 serd_env_free(env);
512 throw RDFException
513 (QString("Failed to import model from URL: %1")
514 .arg(serdStatusToString(rv)),
515 url.toString());
516 }
517
518 serd_reader_free(reader);
519
520 } else {
521
522 // ImportFailOnDuplicates and ImportIgnoreDuplicates:
523 // import into a separate model and transfer across
524
525 SordModel *im = sord_new(m_w.getWorld(), 0, false); // no index
526
527 SerdReader *reader = sord_new_reader(im, env, SERD_TURTLE, NULL);
528
529 // if we have data in the store already, then we must add
530 // a prefix for the new blank nodes we're importing to
531 // disambiguate them
532 if (!doMatch(Triple(), true).empty()) {
533 serd_reader_add_blank_prefix
534 (reader, (uint8_t *)(getNewString().toUtf8().data()));
535 }
536
537 SerdStatus rv = serd_reader_read_file
538 (reader, (const uint8_t *)fileUri.toLocal8Bit().data());
539
540 if (rv != SERD_SUCCESS) {
541 serd_reader_free(reader);
542 sord_free(im);
543 serd_env_free(env);
544 throw RDFException
545 (QString("Failed to import model from URL: %1")
546 .arg(serdStatusToString(rv)),
547 url.toString());
548 }
549
550 serd_reader_free(reader);
551
552 SordQuad templ;
553 tripleToStatement(Triple(), templ);
554
555 if (idm == ImportFailOnDuplicates) {
556
557 SordIter *itr = sord_find(im, templ);
558 while (!sord_iter_end(itr)) {
559 SordQuad q;
560 sord_iter_get(itr, q);
561 if (sord_contains(m_model, q)) {
562 Triple culprit = statementToTriple(q);
563 sord_iter_free(itr);
564 freeStatement(templ);
565 sord_free(im);
566 serd_env_free(env);
567 throw RDFDuplicateImportException("Duplicate statement encountered on import in ImportFailOnDuplicates mode", culprit);
568 }
569 sord_iter_next(itr);
570 }
571 sord_iter_free(itr);
572 }
573
574 SordIter *itr = sord_find(im, templ);
575 while (!sord_iter_end(itr)) {
576 SordQuad q;
577 sord_iter_get(itr, q);
578 if (idm == ImportFailOnDuplicates || // (already tested if so)
579 !sord_contains(m_model, q)) {
580 sord_add(m_model, q);
581 }
582 sord_iter_next(itr);
583 }
584 sord_iter_free(itr);
585 freeStatement(templ);
586 sord_free(im);
587 }
588
589 serd_env_foreach(env, addPrefixSink, this);
590 serd_env_free(env);
591 }
592
593 private:
594 class World
595 {
596 public:
World()597 World() {
598 QMutexLocker locker(&m_mutex);
599 if (!m_world) {
600 m_world = sord_world_new();
601 }
602 ++m_refcount;
603 }
~World()604 ~World() {
605 QMutexLocker locker(&m_mutex);
606 DQ_DEBUG << "~World: About to lower refcount from " << m_refcount << endl;
607 if (--m_refcount == 0) {
608 DQ_DEBUG << "Freeing world" << endl;
609 sord_world_free(m_world);
610 m_world = 0;
611 }
612 }
613
getWorld() const614 SordWorld *getWorld() const {
615 return m_world;
616 }
617
618 private:
619 static QMutex m_mutex;
620 static SordWorld *m_world;
621 static int m_refcount;
622 };
623
624 World m_w;
625 SordModel *m_model;
626 static QMutex m_backendLock; // assume the worst
627
628 typedef QHash<QString, Uri> PrefixMap;
629 Uri m_baseUri;
630 PrefixMap m_prefixes;
631 mutable QMutex m_prefixLock; // also protects m_baseUri
632
doAdd(Triple t)633 bool doAdd(Triple t) {
634 SordQuad statement;
635 tripleToStatement(t, statement);
636 if (!checkComplete(statement)) {
637 throw RDFException("Failed to add triple (statement is incomplete)");
638 }
639 if (sord_contains(m_model, statement)) {
640 return false;
641 }
642 sord_add(m_model, statement);
643 freeStatement(statement);
644 return true;
645 }
646
doRemove(Triple t)647 bool doRemove(Triple t) {
648 SordQuad statement;
649 tripleToStatement(t, statement);
650 if (!checkComplete(statement)) {
651 throw RDFException("Failed to remove triple (statement is incomplete)");
652 }
653 if (!sord_contains(m_model, statement)) {
654 return false;
655 }
656 sord_remove(m_model, statement);
657 freeStatement(statement);
658 return true;
659 }
660
uriToSordNode(Uri uri) const661 SordNode *uriToSordNode(Uri uri) const {
662 SordNode *node = sord_new_uri
663 (m_w.getWorld(),
664 (const unsigned char *)uri.toString().toUtf8().data());
665 if (!node) throw RDFInternalError("Failed to convert URI to internal representation", uri);
666 return node;
667 }
668
669 //!!! utility function to extract string value from node would be more useful
sordNodeToUri(const SordNode * n) const670 Uri sordNodeToUri(const SordNode *n) const {
671 if (!n || sord_node_get_type(n) != SORD_URI) {
672 return Uri();
673 }
674 const uint8_t *s = sord_node_get_string(n);
675 if (s) return Uri(QString::fromUtf8((char *)s));
676 else return Uri();
677 }
678
nodeToSordNode(Node v) const679 SordNode *nodeToSordNode(Node v) const { // called with m_backendLock held
680 SordNode *node = 0;
681 switch (v.type) {
682 case Node::Nothing:
683 return 0;
684 case Node::Blank: {
685 QByteArray b = v.value.toUtf8();
686 const unsigned char *bident = (const unsigned char *)b.data();
687 node = sord_new_blank(m_w.getWorld(), bident);
688 if (!node) throw RDFException
689 ("Failed to construct node from blank identifier",
690 v.value);
691 }
692 break;
693 case Node::URI: {
694 node = uriToSordNode(Uri(v.value));
695 if (!node) throw RDFException("Failed to construct node from URI");
696 }
697 break;
698 case Node::Literal: {
699 QByteArray b = v.value.toUtf8();
700 const unsigned char *literal = (const unsigned char *)b.data();
701 if (v.datatype != Uri()) {
702 SordNode *typeNode = uriToSordNode(v.datatype);
703 node = sord_new_literal(m_w.getWorld(), typeNode, literal, 0);
704 if (!node) throw RDFException
705 ("Failed to construct node from literal of type ",
706 v.datatype);
707 } else {
708 node = sord_new_literal(m_w.getWorld(), 0, literal, 0);
709 if (!node) throw RDFException
710 ("Failed to construct node from literal");
711 }
712 }
713 break;
714 }
715 return node;
716 }
717
sordNodeToNode(const SordNode * node) const718 Node sordNodeToNode(const SordNode *node) const {
719
720 Node v;
721 if (!node) return v;
722
723 SordNodeType type = sord_node_get_type(node);
724
725 switch (type) {
726
727 case SORD_URI:
728 v.type = Node::URI;
729 v.value = sordNodeToUri(node).toString();
730 break;
731
732 case SORD_BLANK:
733 v.type = Node::Blank;
734 //!!! utility function for this -- types and what if it's null?
735 v.value = QString::fromUtf8((char *)sord_node_get_string(node));
736 break;
737
738 case SORD_LITERAL:
739 v.type = Node::Literal;
740 //!!! utility function for this -- types and what if it's null?
741 v.value = QString::fromUtf8((char *)sord_node_get_string(node));
742 v.datatype = sordNodeToUri(sord_node_get_datatype(node));
743 break;
744 }
745
746 return v;
747 }
748
tripleToStatement(Triple t,SordQuad q) const749 void tripleToStatement(Triple t, SordQuad q) const {
750 q[0] = nodeToSordNode(t.a);
751 q[1] = nodeToSordNode(t.b);
752 q[2] = nodeToSordNode(t.c);
753 q[3] = 0;
754 }
755
freeStatement(SordQuad q) const756 void freeStatement(SordQuad q) const {
757 // Not for removing statements from the store
758 for (int i = 0; i < 4; ++i) {
759 sord_node_free(m_w.getWorld(), (SordNode *)q[i]);
760 }
761 }
762
statementToTriple(const SordQuad q) const763 Triple statementToTriple(const SordQuad q) const {
764 Triple triple(sordNodeToNode(q[0]),
765 sordNodeToNode(q[1]),
766 sordNodeToNode(q[2]));
767 return triple;
768 }
769
checkComplete(const SordQuad q) const770 bool checkComplete(const SordQuad q) const {
771 if (!q[0] || !q[1] || !q[2]) {
772 std::cerr << "BasicStore::checkComplete: WARNING: RDF statement contains one or more NULL nodes" << std::endl;
773 return false;
774 }
775 if ((sord_node_get_type(q[0]) == SORD_URI ||
776 sord_node_get_type(q[0]) == SORD_BLANK) &&
777 (sord_node_get_type(q[1]) == SORD_URI)) {
778 return true;
779 } else {
780 std::cerr << "BasicStore::checkComplete: WARNING: RDF statement is incomplete: [" << sord_node_get_string(q[0]) << "," << sord_node_get_string(q[1]) << "," << sord_node_get_string(q[2]) << "]" << std::endl;
781 return false;
782 }
783 }
784
addToSerdNamespace(SerdEnv * env,QString key,QString value) const785 void addToSerdNamespace(SerdEnv *env, QString key, QString value) const {
786
787 QByteArray b = key.toUtf8();
788 QByteArray v = value.toUtf8();
789
790 SerdNode name = serd_node_from_string(SERD_URI, (uint8_t *)b.data());
791 SerdNode uri = serd_node_from_string(SERD_URI, (uint8_t *)v.data());
792
793 serd_env_set_prefix(env, &name, &uri); // copies name, uri
794 }
795
doMatch(Triple t,bool single=false) const796 Triples doMatch(Triple t, bool single = false) const {
797 // Any of a, b, and c in t that have Nothing as their node type
798 // will contribute all matching nodes to the returned triples
799 Triples results;
800 SordQuad templ;
801 tripleToStatement(t, templ);
802 SordIter *itr = sord_find(m_model, templ);
803 while (!sord_iter_end(itr)) {
804 SordQuad q;
805 sord_iter_get(itr, q);
806 results.push_back(statementToTriple(q));
807 if (single) break;
808 sord_iter_next(itr);
809 }
810 sord_iter_free(itr);
811 freeStatement(templ);
812 return results;
813 }
814
serdStatusToString(SerdStatus s)815 QString serdStatusToString(SerdStatus s)
816 {
817 switch (s) {
818 case SERD_SUCCESS: return "Success";
819 case SERD_FAILURE: return "Non-fatal failure";
820 case SERD_ERR_UNKNOWN: return "Unknown error";
821 case SERD_ERR_BAD_SYNTAX: return "Invalid syntax";
822 case SERD_ERR_NOT_FOUND: return "Not found";
823 case SERD_ERR_BAD_ARG: return "Bad argument";
824 case SERD_ERR_ID_CLASH: return "Blank node ID clash";
825 case SERD_ERR_BAD_CURIE: return "Bad abbreviated URI";
826 case SERD_ERR_INTERNAL: return "Internal error in Serd";
827 }
828 return QString("Unknown Serd error type");
829 }
830 };
831
832 QMutex
833 BasicStore::D::m_backendLock;
834
835 QMutex
836 BasicStore::D::World::m_mutex;
837
838 SordWorld *
839 BasicStore::D::World::m_world = 0;
840
841 int
842 BasicStore::D::World::m_refcount = 0;
843
BasicStore()844 BasicStore::BasicStore() :
845 m_d(new D())
846 {
847 }
848
~BasicStore()849 BasicStore::~BasicStore()
850 {
851 delete m_d;
852 }
853
854 void
setBaseUri(Uri uri)855 BasicStore::setBaseUri(Uri uri)
856 {
857 m_d->setBaseUri(uri);
858 }
859
860 Uri
getBaseUri() const861 BasicStore::getBaseUri() const
862 {
863 return m_d->getBaseUri();
864 }
865
866 void
clear()867 BasicStore::clear()
868 {
869 m_d->clear();
870 }
871
872 bool
add(Triple t)873 BasicStore::add(Triple t)
874 {
875 return m_d->add(t);
876 }
877
878 bool
remove(Triple t)879 BasicStore::remove(Triple t)
880 {
881 return m_d->remove(t);
882 }
883
884 void
change(ChangeSet t)885 BasicStore::change(ChangeSet t)
886 {
887 m_d->change(t);
888 }
889
890 void
revert(ChangeSet t)891 BasicStore::revert(ChangeSet t)
892 {
893 m_d->revert(t);
894 }
895
896 bool
contains(Triple t) const897 BasicStore::contains(Triple t) const
898 {
899 return m_d->contains(t);
900 }
901
902 Triples
match(Triple t) const903 BasicStore::match(Triple t) const
904 {
905 return m_d->match(t);
906 }
907
908 void
addPrefix(QString prefix,Uri uri)909 BasicStore::addPrefix(QString prefix, Uri uri)
910 {
911 m_d->addPrefix(prefix, uri);
912 }
913
914 ResultSet
query(QString sparql) const915 BasicStore::query(QString sparql) const
916 {
917 return m_d->query(sparql);
918 }
919
920 Node
complete(Triple t) const921 BasicStore::complete(Triple t) const
922 {
923 return m_d->complete(t);
924 }
925
926 Triple
matchOnce(Triple t) const927 BasicStore::matchOnce(Triple t) const
928 {
929 return m_d->matchOnce(t);
930 }
931
932 Node
queryOnce(QString sparql,QString bindingName) const933 BasicStore::queryOnce(QString sparql, QString bindingName) const
934 {
935 return m_d->queryOnce(sparql, bindingName);
936 }
937
938 Uri
getUniqueUri(QString prefix) const939 BasicStore::getUniqueUri(QString prefix) const
940 {
941 return m_d->getUniqueUri(prefix);
942 }
943
944 Uri
expand(QString uri) const945 BasicStore::expand(QString uri) const
946 {
947 return m_d->expand(uri);
948 }
949
950 Node
addBlankNode()951 BasicStore::addBlankNode()
952 {
953 return m_d->addBlankNode();
954 }
955
956 void
save(QString filename) const957 BasicStore::save(QString filename) const
958 {
959 m_d->save(filename);
960 }
961
962 void
import(QUrl url,ImportDuplicatesMode idm,QString format)963 BasicStore::import(QUrl url, ImportDuplicatesMode idm, QString format)
964 {
965 m_d->import(url, idm, format);
966 }
967
968 BasicStore *
load(QUrl url,QString format)969 BasicStore::load(QUrl url, QString format)
970 {
971 BasicStore *s = new BasicStore();
972 QString su = url.toString();
973 Uri baseUri(su.replace(" ", "%20"));
974 s->setBaseUri(baseUri);
975 // store is empty, ImportIgnoreDuplicates is faster
976 s->import(url, ImportIgnoreDuplicates, format);
977 return s;
978 }
979
980 BasicStore::Features
getSupportedFeatures() const981 BasicStore::getSupportedFeatures() const
982 {
983 Features fs;
984 fs << ModifyFeature;
985 return fs;
986 }
987
988 }
989
990 #endif
991
992
993
994