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