Repository: SINTEF/dlite (language: python)

Issue:
Invalid db.xml file can crash rdf plugin

Add datatype to collection
# Description
Add datatype to collections. This is needed for representing RDF literals.

Also added more tests and fixed bugs.

Closes #741

## Type of change
- [x] Bug fix & code cleanup
- [x] New feature
- [ ] Documentation update
- [x] Test update

## Checklist for the reviewer
This checklist should be used as a help for the reviewer.

- [ ] Is the change limited to one issue?
- [ ] Does this PR close the issue?
- [ ] Is the code easy to read and understand?
- [ ] Do all new feature have an accompanying new test?
- [ ] Has the documentation been updated as necessary?



Discussion / hints:

Fix patch:
diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i
index a8392dc00..68a77961b 100644
--- a/bindings/python/dlite-collection-python.i
+++ b/bindings/python/dlite-collection-python.i
@@ -11,15 +11,21 @@
             v = self.next()
             if v and v.is_meta:
                 v.__class__ = Metadata
-        elif self.rettype in 'RTspo':  # relation
+        elif self.rettype in 'RTtspod':  # relation
             v = self.next_relation()
         else:
-            raise ValueError('`rettype` must one of "IRTspo"')
+            raise ValueError('`rettype` must one of "IRTtspod"')
 
         if v is None:
             raise StopIteration()
 
-        if self.rettype == 'T':  # return (s,p,o) tuple
+        if self.rettype == 'I':  # return instance
+            return v
+        if self.rettype == 'R':  # return relation object
+            return v.copy()
+        if self.rettype == 'T':  # return (s,p,o,d) tuple
+            return (v.s, v.p, v.o, v.d)
+        if self.rettype == 't':  # return (s,p,o) tuple
             return (v.s, v.p, v.o)
         elif self.rettype == 's':  # return subject
             return v.s
@@ -27,8 +33,10 @@
             return v.p
         elif self.rettype == 'o':  # return object
             return v.o
+        elif self.rettype == 'd':  # return datatype
+            return v.d
 
-        return v
+        raise _dlite.DLiteRuntimeError()  # should never be reached
 %}
 
 }
@@ -49,9 +57,17 @@ class Collection(Instance):
     of instances.
 
     Use the get_relations() method (or the convenience methods
-    get_subjects(), get_predicates() and get_predicates()) to iterate
-    over relations.  The number of relations is available via the `nrelations`
-    property.
+    get_subjects(), get_predicates() and get_objects()) to iterate
+    over relations.  The number of relations is available via the
+    `nrelations` property.
+
+    Relations are (s, p, o, d=None)-triples with an optional fourth field
+    `d`, specifying the datatype of the object.  The datatype may have the
+    following values:
+      - None: object is an IRI.
+      - Starts with '@': object is a language-tagged plain literal.
+        The language identifier follows the '@'-sign.
+      - Otherwise: object is a literal with datatype `d`.
     """
     def __new__(cls, id=None):
         """Creates an empty collection."""
@@ -191,21 +207,22 @@ class Collection(Instance):
         b = _collection_has_id(self._coll, id)
         return bool(b)
 
-    def add_relation(self, s, p, o):
+    def add_relation(self, s, p, o, d=None):
         """Add (subject, predicate, object) RDF triple to collection."""
-        if _collection_add_relation(self._coll, s, p, o) != 0:
-            raise _dlite.DLiteError(f'Error adding relation ({s}, {p}, {o})')
+        if _collection_add_relation(self._coll, s, p, o, d) != 0:
+            raise _dlite.DLiteError(
+                f'Error adding relation ({s}, {p}, {o}, {d})')
 
-    def remove_relations(self, s=None, p=None, o=None):
+    def remove_relations(self, s=None, p=None, o=None, d=None):
         """Remove all relations matching `s`, `p` and `o`."""
-        if _collection_remove_relations(self._coll, s, p, o) < 0:
+        if _collection_remove_relations(self._coll, s, p, o, d) < 0:
             raise _dlite.DLiteError(
-                f'Error removing relations matching ({s}, {p}, {o})')
+                f'Error removing relations matching ({s}, {p}, {o}, {d})')
 
-    def get_first_relation(self, s=None, p=None, o=None):
+    def get_first_relation(self, s=None, p=None, o=None, d=None):
         """Returns the first relation matching `s`, `p` and `o`.
         None is returned if there are no matching relations."""
-        return _collection_find_first(self._coll, s, p, o)
+        return _collection_find_first(self._coll, s, p, o, d)
 
     def get_instances(self, metaid=None, property_mappings=False,
                       allow_incomplete=False, ureg=None, function_repo=None,
@@ -295,26 +312,58 @@ class Collection(Instance):
         """Returns a generator over all labels."""
         return self.get_subjects(p='_is-a', o='Instance')
 
-    def get_relations(self, s=None, p=None, o=None, rettype='T'):
+    def get_relations(self, s=None, p=None, o=None, d=None, rettype='t'):
         """Returns a generator over all relations matching the given
-        values of `s`, `p` and `o`."""
-        return _CollectionIter(self, s=s, p=p, o=o, rettype=rettype)
+        values of `s` (subject), `p` (predicate), `o` (object) and `d`
+        (datatype). See docstring for dlite.Collection for more info.
+
+        The following values for `rettype` are supported:
+        - 'I': Iterate over instances.
+        - 'R': Iterate over relation objects.
+        - 'T': Iterate over (s,p,o,d) tuples.
+        - 't': Iterate over (s,p,o) tuples.
+        - 's': Iterate over subjects.
+        - 'p': Iterate over predicates.
+        - 'o': Iterate over objects.
+        - 'd': Iterate over datatypes.
+        """
+        return _CollectionIter(self, s=s, p=p, o=o, d=d, rettype=rettype)
 
-    def get_subjects(self, p=None, o=None):
+    def get_subjects(self, p=None, o=None, d=None):
         """Returns a generator over all subjects of relations matching the
-        given values of `p` and `o`."""
-        return _CollectionIter(self, s=None, p=p, o=o, rettype='s')
+        given values of `p`, `o` and `d`."""
+        return _CollectionIter(self, s=None, p=p, o=o, d=d, rettype='s')
 
-    def get_predicates(self, s=None, o=None):
+    def get_predicates(self, s=None, o=None, d=None):
         """Returns a generator over all predicates of relations matching the
-        given values of `s` and `o`."""
-        return _CollectionIter(self, s=s, p=None, o=o, rettype='p')
+        given values of `s`, `o` and `d`."""
+        return _CollectionIter(self, s=s, p=None, o=o, d=d, rettype='p')
 
-    def get_objects(self, s=None, p=None):
+    def get_objects(self, s=None, p=None, d=None):
         """Returns a generator over all subjects of relations matching the
-        given values of `s` and `p`."""
-        return _CollectionIter(self, s=s, p=p, o=None, rettype='o')
+        given values of `s`, `p` and `d`."""
+        return _CollectionIter(self, s=s, p=p, o=None, d=d, rettype='o')
+
+    def value(self, s=None, p=None, o=None, d=None, default=None, any=False):
+        """Return pointer to the value for a pair of two criteria.
+
+        Useful if one knows that there may only be one value.
 
+        Parameters:
+            s, p, o: Criteria to match. Two of these must not be None.
+            d: If not None, the required datatype of literal objects.
+            default: Value to return if no matches are found.
+            any: If true, return first matching value.
+
+        Returns:
+            A pointer to the value of the `s`, `p` or `o` that is None.
+
+        Raises:
+            DLiteTypeError: Not exactly two of `s`, `p` and `o` are None.
+            DLiteSearchError: No match can be found or more that one match
+                if `any` is true.
+        """
+        return _dlite._collection_value(self, s, p, o, d, default, any)
 
 def get_collection(id):
     """Returns a new reference to a collection with given id."""
diff --git a/bindings/python/dlite-collection.i b/bindings/python/dlite-collection.i
index 80b043798..441472c2d 100644
--- a/bindings/python/dlite-collection.i
+++ b/bindings/python/dlite-collection.i
@@ -9,12 +9,27 @@
 struct _DLiteCollection *_get_collection(const char *id)
 {
   struct _DLiteInstance *inst = dlite_instance_get(id);
-  if (!inst) return dlite_err(1, "no instance with this id: %s", id), NULL;
+  if (!inst) return dlite_err(dliteTypeError,
+                              "no instance with this id: %s", id), NULL;
   if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
     return dlite_err(1, "not a collection: %s", id), NULL;
   return (DLiteCollection *)inst;
 }
 
+char *_collection_value(DLiteInstance *inst,
+                        const char *s, const char *p,
+                        const char *o, const char *d,
+                        const char *fallback, int any) {
+  if (!inst) return dlite_err(dliteTypeError,
+                              "first argument must be provided"), NULL;
+  if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
+    return dlite_err(dliteTypeError,
+                     "first argument must be a collection"), NULL;
+  DLiteCollection *coll = (DLiteCollection *)inst;
+  const char *v = triplestore_value(coll->rstore, s, p, o, d, fallback, any);
+  return (v) ? strdup(v) : NULL;
+ }
+
 %}
 
 
@@ -26,13 +41,15 @@ int dlite_collection_save(struct _DLiteCollection *coll,
                           struct _DLiteStorage *s);
 int dlite_collection_save_url(struct _DLiteCollection *coll, const char *url);
 int dlite_collection_add_relation(struct _DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o);
+                                  const char *p, const char *o,
+                                  const char *d=NULL);
 int dlite_collection_remove_relations(struct _DLiteCollection *coll,
-                                      const char *s,
-                                      const char *p, const char *o);
+                                      const char *s, const char *p,
+                                      const char *o, const char *d=NULL);
 const struct _Triple *
   dlite_collection_find_first(const struct _DLiteCollection *coll,
-                              const char *s, const char *p, const char *o);
+                              const char *s, const char *p, const char *o,
+                              const char *d=NULL);
 int dlite_collection_add(struct _DLiteCollection *coll, const char *label,
                          struct _DLiteInstance *inst);
 int dlite_collection_remove(struct _DLiteCollection *coll, const char *label);
@@ -86,6 +103,7 @@ struct _DLiteCollection {
     dlite_instance_incref(inst);
     return inst;
   }
+
 }
 
 
@@ -97,8 +115,11 @@ struct _DLiteCollection {
   struct _CollectionIter {
     DLiteCollection *coll;
     DLiteCollectionState state;
-    char *s, *p, *o;  /* search pattern */
-    char rettype;     /* return type: I=Instance, R=Relation, T=tuple */
+    char *s, *p, *o, *d;  /* search pattern */
+    char rettype;         /* return type: I=Instance; R=Relation;
+                             T=(s,p,o,d)-tuple; t=(s,p,o)-tuple;
+                             s=subject; p=predicate; o=object; d=datatype */
+
   };
 %}
 
@@ -108,7 +129,7 @@ struct _DLiteCollection {
 %extend struct _CollectionIter {
   _CollectionIter(struct _DLiteInstance *inst,
                   const char *s=NULL, const char *p=NULL, const char *o=NULL,
-                  const char rettype='T') {
+                  const char *d=NULL, const char rettype='t') {
     if (strcmp(inst->meta->uri, DLITE_COLLECTION_ENTITY) != 0)
       return dlite_err(1, "not a collection: %s", inst->uuid), NULL;
     struct _CollectionIter *iter = calloc(1, sizeof(struct _CollectionIter));
@@ -117,6 +138,7 @@ struct _DLiteCollection {
     iter->s = (s) ? strdup(s) : NULL;
     iter->p = (p) ? strdup(p) : NULL;
     iter->o = (o) ? strdup(o) : NULL;
+    iter->d = (d) ? strdup(d) : NULL;
     iter->rettype = rettype;
     dlite_collection_init_state(coll, &iter->state);
     return iter;
@@ -127,6 +149,7 @@ struct _DLiteCollection {
     if ($self->s) free($self->s);
     if ($self->p) free($self->p);
     if ($self->o) free($self->o);
+    if ($self->d) free($self->d);
     free($self);
   }
 
@@ -142,7 +165,7 @@ struct _DLiteCollection {
            ) next;
   const struct _Triple *next_relation(void) {
     return dlite_collection_find($self->coll, &$self->state,
-                                 $self->s, $self->p, $self->o);
+                                 $self->s, $self->p, $self->o, $self->d);
   }
 
   %feature("docstring",
@@ -171,6 +194,13 @@ struct _DLiteCollection {
 %newobject _get_collection;
 struct _DLiteCollection *_get_collection(const char *id);
 
+%feature("docstring", "Return the value for a pair of two criteria.") _value;
+%newobject _value;
+char *_collection_value(struct _DLiteInstance *inst,
+                        const char *s=NULL, const char *p=NULL,
+                        const char *o=NULL, const char *d=NULL,
+                        const char *fallback=NULL, int any=0);
+
 
 /* -----------------------------------
  * Target language-spesific extensions
diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i
index de4360b2a..f907613b7 100644
--- a/bindings/python/dlite-entity-python.i
+++ b/bindings/python/dlite-entity-python.i
@@ -22,11 +22,11 @@ class Metadata(Instance):
         description: Description of metadata.
     """
     def __new__(
-            cls,
-            uri: str,
-            dimensions: "Sequence[Dimension]",
-            properties: "Sequence[Property]",
-            description: str = ''
+        cls,
+        uri: str,
+        dimensions: "Sequence[Dimension]",
+        properties: "Sequence[Property]",
+        description: str = ''
     ):
         return Instance.create_metadata(
             uri, dimensions, properties, description)
@@ -262,8 +262,22 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
 %extend _Triple {
   %pythoncode %{
     def __repr__(self):
-        return 'Relation(s=%r, p=%r, o=%r, id=%r)' % (
-            self.s, self.p, self.o, self.id)
+        args = [f"s='{self.s}', p='{self.p}', o='{self.o}'"]
+        if self.d:
+            args.append(f"d='{self.d}'")
+        if self.id:
+            args.append(f"id='{self.id}'")
+        return f"Relation({', '.join(args)})"
+
+    def __eq__(self, other):
+        if isinstance(other, Relation):
+            return (self.s == other.s and self.p == other.p and
+                    self.o == other.o and self.d == other.d)
+        return NotImplemented
+
+    def copy(self):
+        """Returns a copy of self."""
+        return Relation(s=self.s, p=self.p, o=self.o, d=self.d, id=self.id)
 
     def aspreferred(self):
         """Returns preferred Python representation."""
@@ -274,7 +288,6 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         d = dict(s=self.s, p=self.p, o=self.o)
         if self.id:
             d[id] = self.id
-
         return d
 
     def asstrings(self):
@@ -771,6 +784,4 @@ def get_instance(id: str, metaid: str = None, check_storages: bool = True) -> "I
         return self.copy()
 
 %}
-
-
 }
diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i
index 7716824a1..b2bc647a2 100644
--- a/bindings/python/dlite-entity.i
+++ b/bindings/python/dlite-entity.i
@@ -192,19 +192,38 @@ struct _DLiteProperty {
 /* --------
  * Relation
  * -------- */
+%feature("docstring", "\
+Relations in DLite corresponds to RDF-triples, but are internally 4 fields:
+  - s: subject
+  - p: predicate
+  - o: object
+  - d: datatype
+
+The datatype is the datatype for literal objects. It may have three forms:
+  - None: object is an IRI (rdfs:Resource).
+  - Starts with '@': object is a language-tagged plain literal
+    (rdf:langString). The language identifier follows the '@'-sign.
+    Ex: '@en' for english.
+  - Otherwise: object is a literal with datatype `d`. Ex: 'xsd:int'.
+
+As an internal implementation detail, relations also have an `id` field.
+It may change in the future, so please don't rely on it.
+") _Triple;
 %rename(Relation) _Triple;
 struct _Triple {
   char *s;     /*!< subject */
   char *p;     /*!< predicate */
   char *o;     /*!< object */
+  char *d;     /*!< datatype */
   char *id;    /*!< unique ID identifying this triple */
 };
 
 %extend _Triple {
-  _Triple(const char *s, const char *p, const char *o, const char *id=NULL) {
+  _Triple(const char *s, const char *p, const char *o,
+          const char *d=NULL, const char *id=NULL) {
     Triple *t;
     if (!(t =  calloc(1, sizeof(Triple)))) FAIL("allocation failure");
-    if (triple_set(t, s, p, o, id)) FAIL("cannot set relation");
+    if (triple_set(t, s, p, o, d, id)) FAIL("cannot set relation");
     return t;
   fail:
     if (t) {
@@ -220,19 +239,6 @@ struct _Triple {
   }
 }
 
-%{
-char *triple_get_id2(const char *s, const char *p, const char *o,
-                      const char *namespace) {
-  return triple_get_id(namespace, s, p, o);
-}
-%}
- %rename(triple_get_id) triple_get_id2;
-%newobject triple_get_id;
-char *triple_get_id(const char *s, const char *p, const char *o,
-                     const char *namespace=NULL);
-
-void triple_set_default_namespace(const char *namespace);
-
 
 /* --------
  * Instance
@@ -289,12 +295,15 @@ struct _DLiteInstance {
       DLiteMeta *meta;
       size_t i, *d, n=ndims;
       if (!(meta = dlite_meta_get(metaid)))
-        return dlite_err(1, "cannot find metadata '%s'", metaid), NULL;
+        return dlite_err(dliteMissingMetadataError,
+                         "cannot find metadata '%s'", metaid), NULL;
       if (n != meta->_ndimensions) {
         dlite_err(dliteValueError, "ndims=%d, but %s has %u dimension(s)",
                   ndims, metaid, (unsigned)meta->_ndimensions);
         dlite_meta_decref(meta);
-        return NULL;
+        return dlite_err(dliteParseError, "%s has %u dimensions (%u given)",
+                         metaid, (unsigned)meta->_ndimensions,
+                         (unsigned)n), NULL;
       }
       d = malloc(n * sizeof(size_t));
       for (i=0; i<n; i++) d[i] = dims[i];
diff --git a/bindings/python/dlite-python.i b/bindings/python/dlite-python.i
index 55f37f0d3..61a12f1cc 100644
--- a/bindings/python/dlite-python.i
+++ b/bindings/python/dlite-python.i
@@ -931,33 +931,39 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
       void *p;
       if (SWIG_IsOK(SWIG_ConvertPtr(obj, &p, SWIGTYPE_p__Triple, 0))) {
         DLiteRelation *src = (DLiteRelation *)p;
-        triple_reset(ptr, src->s, src->p, src->o, src->id);
+        triple_reset(ptr, src->s, src->p, src->o, src->d, src->id);
 
       } else if (PySequence_Check(obj) || PyIter_Check(obj)) {
         int i;
         Py_ssize_t n;
         PyObject *lst;
-        char **s = ptr;  /* cast DLiteRelation to 4 string pointers */
-        assert(sizeof(DLiteRelation) == 4*sizeof(char *));
+        char **s = ptr;  /* cast DLiteRelation to 5 string pointers */
+        assert(sizeof(DLiteRelation) == 5*sizeof(char *));
 
-        /* Check that `obj` is a sequence of 3 or 4 strings */
+        /* Check that `obj` is a sequence of 3 to 5 strings */
         if (!(lst = PySequence_Fast(obj, "not a sequence or iterable")))
           FAIL("expected relation to be represented by a sequence or iterable");
 
-        if ((n = PySequence_Fast_GET_SIZE(lst)) < 3 || n > 4) {
+        if ((n = PySequence_Fast_GET_SIZE(lst)) < 3 || n > 5) {
           Py_DECREF(lst);
-          FAIL1("relations must be 3 or 4 strings, got %ld", (long)n);
+          FAIL1("relations must be 3 to 5 strings, got %ld", (long)n);
         }
         for (i=0; i<n; i++) {
           PyObject *item = PySequence_Fast_GET_ITEM(lst, i);
           if (!PyUnicode_Check(item)) {
             Py_DECREF(lst);
-            FAIL("relation subject, predicate and object must be strings");
+            FAIL("all elements in a (subject(s), predicate(p), object(o)[, "
+                 "datatype(d)[, id]]) relation provided as a sequence must "
+                 "be strings");
           }
         }
 
         /* Free old values stored in the relation */
-        for (i=0; i<4; i++) if (s[i]) free(s[i]);
+        for (i=0; i<5; i++)
+          if (s[i]) {
+            free(s[i]);
+            s[i] = NULL;
+          }
 
         /* Assign new values */
         for (i=0; i<n; i++) {
@@ -969,14 +975,14 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
         }
 
         /* Assign id if not already provided */
-        if (n < 4)
-          s[3] = triple_get_id(NULL, s[0], s[1], s[2]);
+        if (n < 5)
+          s[4] = triple_get_id(NULL, s[0], s[1], s[2], s[3]);
         Py_DECREF(lst);
 
       } else if (PyMapping_Check(obj)) {
         char *msg=NULL;
-        PyObject *s=NULL, *p=NULL, *o=NULL, *id=NULL;
-        const char *ss, *sp, *so, *sid;
+        PyObject *s=NULL, *p=NULL, *o=NULL, *d=NULL, *id=NULL;
+        const char *ss, *sp, *so, *sd, *sid;
         if (!(s = PyMapping_GetItemString(obj, "s")) ||
             !(p = PyMapping_GetItemString(obj, "p")) ||
             !(o = PyMapping_GetItemString(obj, "o")))
@@ -985,15 +991,21 @@ int dlite_swig_set_scalar(void *ptr, DLiteType type, size_t size, obj_t *obj)
                       PyUnicode_Check(p) && (sp = PyUnicode_AsUTF8(p)) &&
                       PyUnicode_Check(o) && (so = PyUnicode_AsUTF8(o))))
           msg = "Relation 's', 'p', 'o' items must be strings";
+        if (!msg && PyMapping_HasKeyString(obj, "d") &&
+            (!(d = PyMapping_GetItemString(obj, "d")) ||
+             !PyUnicode_Check(d) || !(sd = PyUnicode_AsUTF8(d))))
+          msg = "If given, relation d (datatype) must be a string";
         if (!msg && PyMapping_HasKeyString(obj, "id") &&
             (!(id = PyMapping_GetItemString(obj, "id")) ||
              !PyUnicode_Check(id) || !(sid = PyUnicode_AsUTF8(id))))
           msg = "If given, relation id must be a string";
         if (!msg)
-          triple_reset(ptr, ss, sp, so, (id) ? sid : NULL);
+          triple_reset(ptr, ss, sp, so,
+                       (d) ? sd : NULL, (id) ? sid : NULL);
         Py_XDECREF(s);
         Py_XDECREF(p);
         Py_XDECREF(o);
+        Py_XDECREF(d);
         Py_XDECREF(id);
         if (msg) FAIL1("%s", msg);
 
diff --git a/src/dlite-collection.c b/src/dlite-collection.c
index f32a2ec6a..9b8bbe77f 100644
--- a/src/dlite-collection.c
+++ b/src/dlite-collection.c
@@ -42,7 +42,8 @@ int dlite_collection_deinit(DLiteInstance *inst)
 
   /* Release references to all instances. */
   dlite_collection_init_state(coll, &state);
-  while ((r=dlite_collection_find(coll,&state, NULL, "_has-uuid", NULL))) {
+  while ((r=dlite_collection_find(coll,&state, NULL, "_has-uuid", NULL,
+                                  NULL))) {
     if ((inst2 = dlite_instance_get(r->o))) {
       dlite_instance_decref(inst2);
     } else {
@@ -63,6 +64,9 @@ int _cmp_triple(const void *a, const void *b) {
   if ((v = strcmp(t1->s, t2->s))) return v;
   if ((v = strcmp(t1->p, t2->p))) return v;
   if ((v = strcmp(t1->o, t2->o))) return v;
+  if (t1->d && !t2->d) return 1;
+  if (!t1->d && t2->d) return -1;
+  if (t1->d && t2->d && (v = strcmp(t1->d, t2->d))) return v;
   return 0;
 }
 
@@ -73,6 +77,7 @@ void _sha3_update_triple(sha3_context *c, const Triple *t)
   sha3_Update(c, t->s, strlen(t->s));
   sha3_Update(c, t->p, strlen(t->p));
   sha3_Update(c, t->o, strlen(t->o));
+  if (t->d) sha3_Update(c, t->d, strlen(t->d));
 }
 
 /* Calculate hash of a collection. */
@@ -111,7 +116,8 @@ int dlite_collection_gethash(const DLiteInstance *inst, uint8_t *hash,
     if (strcmp(triples[i]->p, "_has-hash") == 0) continue;
     if (strcmp(triples[i]->p, "_has-uuid") == 0) {
       if ((t = triplestore_find_first(coll->rstore,
-                                      triples[i]->s, "_has-hash", NULL))) {
+                                      triples[i]->s, "_has-hash", NULL,
+                                      NULL))) {
         _sha3_update_triple(&c, t);
       } else {
         uint8_t hash[DLITE_HASH_SIZE];
@@ -166,7 +172,7 @@ int dlite_collection_loadprop(const DLiteInstance *inst, size_t i)
   /* Iterate over all newly added triples and load instances corresponding
      to "_has-uuid" relations. */
   triplestore_init_state(coll->rstore, &state);
-  while ((t = triplestore_find(&state, NULL, "_has-uuid", NULL))) {
+  while ((t = triplestore_find(&state, NULL, "_has-uuid", NULL, NULL))) {
     DLiteInstance *inst2 = dlite_instance_get(t->o);
     if (!inst2) retval = errx(dliteStorageLoadError,
                               "cannot get instance \"%s\" labeled \"%s\" "
@@ -194,6 +200,7 @@ int dlite_collection_saveprop(DLiteInstance *inst, size_t i)
   triplestore_init_state(coll->rstore, &state);
   while ((t = triplestore_next(&state))) {
     assert(j < (int)coll->nrelations);
+    triple_clean(coll->relations + j);
     triple_copy(coll->relations + j, t);
     j++;
   }
@@ -281,8 +288,10 @@ DLiteCollection *dlite_collection_load(DLiteStorage *s, const char *id,
   }
 
   dlite_collection_init_state(coll, &state);
-  while ((t = dlite_collection_find(coll, &state, NULL, "_has-uuid", NULL))) {
-    if (!(t2 = dlite_collection_find_first(coll, t->s, "_has-meta", NULL)))
+  while ((t = dlite_collection_find(coll, &state, NULL, "_has-uuid", NULL,
+                                    NULL))) {
+    if (!(t2 = dlite_collection_find_first(coll, t->s, "_has-meta", NULL,
+                                           NULL)))
       FAILCODE1(dliteInconsistentDataError,
                 "collection inconsistency - no \"_has-meta\" relation for "
                 "instance: %s", t->s);
@@ -369,13 +378,13 @@ int dlite_collection_save_url(DLiteCollection *coll, const char *url)
 }
 
 /*
-  Adds subject-predicate-object relation to collection.  Returns non-zero
-  on error.
+  Adds subject-predicate-object relation to collection.  `d` is the
+  datatype of literal object. Returns non-zero on error.
  */
 int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o)
+                                  const char *p, const char *o, const char *d)
 {
-  int stat = triplestore_add(coll->rstore, s, p, o);
+  int stat = triplestore_add(coll->rstore, s, p, o, d);
   if (!stat)
     DLITE_PROP_DIM(coll, 0, 0) = coll->nrelations;
   return stat;
@@ -387,9 +396,10 @@ int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
   multiple matches.  Returns the number of relations removed, or -1 on error.
  */
 int dlite_collection_remove_relations(DLiteCollection *coll, const char *s,
-                                      const char *p, const char *o)
+                                      const char *p, const char *o,
+                                      const char *d)
 {
-  int retval = triplestore_remove(coll->rstore, s, p, o);
+  int retval = triplestore_remove(coll->rstore, s, p, o, d);
   if (retval > -1)
     DLITE_PROP_DIM(coll, 0, 0) = coll->nrelations;
   return retval;
@@ -417,6 +427,15 @@ void dlite_collection_deinit_state(DLiteCollectionState *state)
 }
 
 
+/*
+  Resets `state` already initialised with dlite_collection_init_state().
+*/
+void dlite_collection_reset_state(DLiteCollectionState *state)
+{
+  triplestore_reset_state((TripleState *)state);
+}
+
+
 /*
   Finds matching relations.
 
@@ -436,12 +455,12 @@ void dlite_collection_deinit_state(DLiteCollectionState *state)
 const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
                                            DLiteCollectionState *state,
                                            const char *s, const char *p,
-                                           const char *o)
+                                           const char *o, const char *d)
 {
   if (state)
-    return (DLiteRelation *)triplestore_find((TripleState *)state, s, p, o);
+    return (DLiteRelation *)triplestore_find((TripleState *)state, s, p, o, d);
   else
-    return (DLiteRelation *)triplestore_find_first(coll->rstore, s, p, o);
+    return (DLiteRelation *)triplestore_find_first(coll->rstore, s, p, o, d);
 }
 
 /*
@@ -450,14 +469,9 @@ const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
  */
 const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
                                                  const char *s, const char *p,
-                                                 const char *o)
+                                                 const char *o, const char *d)
 {
-  DLiteCollectionState state;
-  const DLiteRelation *r;
-  dlite_collection_init_state(coll, &state);
-  r = dlite_collection_find(coll, &state, s, p, o);
-  dlite_collection_deinit_state(&state);
-  return r;
+  return dlite_collection_find(coll, NULL, s, p, o, d);
 }
 
 /*
@@ -469,14 +483,15 @@ const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
 int dlite_collection_add_new(DLiteCollection *coll, const char *label,
                              DLiteInstance *inst)
 {
-  if (dlite_collection_find(coll, NULL, label, "_is-a", "Instance"))
+  if (dlite_collection_find(coll, NULL, label, "_is-a", "Instance", NULL))
     return errx(dliteValueError,
                 "instance with label '%s' is already in the collection",
                 label);
 
-  dlite_collection_add_relation(coll, label, "_is-a", "Instance");
-  dlite_collection_add_relation(coll, label, "_has-uuid", inst->uuid);
-  dlite_collection_add_relation(coll, label, "_has-meta", inst->meta->uri);
+  dlite_collection_add_relation(coll, label, "_is-a", "Instance", NULL);
+  dlite_collection_add_relation(coll, label, "_has-uuid", inst->uuid,
+                                "xsd:anyURI");
+  dlite_collection_add_relation(coll, label, "_has-meta", inst->meta->uri, NULL);
   return 0;
 }
 
@@ -502,8 +517,8 @@ int dlite_collection_remove(DLiteCollection *coll, const char *label)
   DLiteCollectionState state;
   DLiteInstance *inst;
   const DLiteRelation *r;
-  if (dlite_collection_remove_relations(coll, label, "_is-a", "Instance") > 0) {
-    r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL);
+  if (dlite_collection_remove_relations(coll, label, "_is-a", "Instance", NULL) > 0) {
+    r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL, NULL);
     assert(r);
 
     /* Removes reference hold by collection to the instance. We have
@@ -523,9 +538,9 @@ int dlite_collection_remove(DLiteCollection *coll, const char *label)
     //*dlite_collection_deinit_state(&state);
     UNUSED(state);
 
-    dlite_collection_remove_relations(coll, label, "_has-uuid", NULL);
-    dlite_collection_remove_relations(coll, label, "_has-meta", NULL);
-    dlite_collection_remove_relations(coll, label, "_has-dimmap", NULL);
+    dlite_collection_remove_relations(coll, label, "_has-uuid", NULL, NULL);
+    dlite_collection_remove_relations(coll, label, "_has-meta", NULL, NULL);
+    dlite_collection_remove_relations(coll, label, "_has-dimmap", NULL, NULL);
     return 0;
   }
   return 1;
@@ -539,7 +554,8 @@ const DLiteInstance *dlite_collection_get(const DLiteCollection *coll,
                                           const char *label)
 {
   const DLiteRelation *r;
-  if ((r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL))) {
+  if ((r = dlite_collection_find(coll, NULL, label, "_has-uuid", NULL,
+                                 NULL))) {
     DLiteInstance *inst = dlite_instance_get(r->o);
     if (!inst) FAILCODE2(dliteKeyError,
                          "no such instance '%s' in collection '%s'",
@@ -566,7 +582,7 @@ const DLiteInstance *dlite_collection_get_id(const DLiteCollection *coll,
   const DLiteRelation *r;
   char uuid[DLITE_UUID_LENGTH+1];
   if (dlite_get_uuid(uuid, id) < 0) return NULL;
-  if ((r = dlite_collection_find(coll, NULL, NULL, "_has-uuid", uuid)))
+  if ((r = dlite_collection_find(coll, NULL, NULL, "_has-uuid", uuid, NULL)))
     return dlite_instance_get(id);
   return NULL;
 }
@@ -598,7 +614,8 @@ DLiteInstance *dlite_collection_get_new(const DLiteCollection *coll,
  */
 int dlite_collection_has(const DLiteCollection *coll, const char *label)
 {
-  return (dlite_collection_find_first(coll, label, "_has-uuid", NULL)) ? 1 : 0;
+  return (dlite_collection_find_first(coll, label, "_has-uuid", NULL,
+                                      NULL)) ? 1 : 0;
 }
 
 /*
@@ -609,7 +626,8 @@ int dlite_collection_has_id(const DLiteCollection *coll, const char *id)
 {
   char uuid[DLITE_UUID_LENGTH+1];
   if (dlite_get_uuid(uuid, id) < 0) return 0;
-  return (dlite_collection_find_first(coll, NULL, "_has-uuid", uuid)) ? 1 : 0;
+  return (dlite_collection_find_first(coll, NULL, "_has-uuid", uuid,
+                                      NULL)) ? 1 : 0;
 }
 
 
@@ -641,7 +659,7 @@ DLiteInstance *dlite_collection_next_new(DLiteCollection *coll,
 {
   UNUSED(coll);
   const Triple *t;
-  while ((t = triplestore_find(state, NULL, "_has-uuid", NULL)))
+  while ((t = triplestore_find(state, NULL, "_has-uuid", NULL, NULL)))
     return dlite_instance_get(t->o);
   return NULL;
 }
@@ -656,7 +674,31 @@ int dlite_collection_count(DLiteCollection *coll)
   int count=0;
   TripleState state;
   triplestore_init_state(coll->rstore, &state);
-  while (triplestore_find(&state, NULL, "_is-a", "Instance")) count++;
+  while (triplestore_find(&state, NULL, "_is-a", "Instance", NULL)) count++;
   triplestore_deinit_state(&state);
   return count;
 }
+
+
+/*
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the collection and should be copied by the user
+  since it may be overwritten by later calls to the collection.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *dlite_collection_value(DLiteCollection *coll, const char *s,
+                                   const char *p, const char *o, const char *d,
+                                   const char *fallback, int any)
+{
+  return triplestore_value(coll->rstore, s, p, o, d, fallback, any);
+}
diff --git a/src/dlite-collection.h b/src/dlite-collection.h
index 077b7e0d6..49c802c2d 100644
--- a/src/dlite-collection.h
+++ b/src/dlite-collection.h
@@ -78,6 +78,11 @@ int dlite_collection_init(DLiteInstance *inst);
  */
 int dlite_collection_deinit(DLiteInstance *inst);
 
+/**
+  Resets `state` already initialised with dlite_collection_init_state().
+*/
+void dlite_collection_reset_state(DLiteCollectionState *state);
+
 /**
   Calculate hash of a collection.
 
@@ -168,11 +173,12 @@ int dlite_collection_save_url(DLiteCollection *coll, const char *url);
 
 
 /**
-  Adds subject-predicate-object relation to collection.  Returns non-zero
-  on error.
+  Adds subject-predicate-object relation to collection.  `d` is the
+  datatype of literal object. Returns non-zero on error.
  */
 int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
-                                  const char *p, const char *o);
+                                  const char *p, const char *o,
+                                  const char *d);
 
 
 /**
@@ -180,7 +186,8 @@ int dlite_collection_add_relation(DLiteCollection *coll, const char *s,
   multiple matches.  Returns the number of relations removed, or -1 on error.
  */
 int dlite_collection_remove_relations(DLiteCollection *coll, const char *s,
-                                      const char *p, const char *o);
+                                      const char *p, const char *o,
+                                      const char *d);
 
 
 /**
@@ -215,7 +222,7 @@ void dlite_collection_deinit_state(DLiteCollectionState *state);
 const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
 					   DLiteCollectionState *state,
 					   const char *s, const char *p,
-					   const char *o);
+					   const char *o, const char *d);
 
 /**
   Like dlite_collection_find(), but returns only a pointer to the
@@ -223,7 +230,7 @@ const DLiteRelation *dlite_collection_find(const DLiteCollection *coll,
  */
 const DLiteRelation *dlite_collection_find_first(const DLiteCollection *coll,
                                                  const char *s, const char *p,
-                                                 const char *o);
+                                                 const char *o, const char *d);
 
 
 /**
@@ -309,4 +316,24 @@ DLiteInstance *dlite_collection_next_new(DLiteCollection *coll,
 int dlite_collection_count(DLiteCollection *coll);
 
 
+/**
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the collection and should be copied by the user
+  since it may be overwritten by later calls to the collection.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *dlite_collection_value(DLiteCollection *coll, const char *s,
+                                   const char *p, const char *o, const char *d,
+                                   const char *fallback, int any);
+
 #endif /* _DLITE_COLLECTION_H */
diff --git a/src/dlite-errors.c b/src/dlite-errors.c
index 00a970d1f..bcb6bdc1f 100644
--- a/src/dlite-errors.c
+++ b/src/dlite-errors.c
@@ -28,6 +28,7 @@ const char *dlite_errname(DLiteErrCode code)
 
   case dliteOSError:                return "DLiteOS";
   case dliteKeyError:               return "DLiteKey";
+  case dliteSearchError:            return "DLiteSearch";
   case dliteParseError:             return "DLiteParse";
   case dliteSerialiseError:         return "DLiteSerialise";
   case dliteUnsupportedError:       return "DLiteUnsupported";
@@ -73,6 +74,7 @@ const char *dlite_errdescr(DLiteErrCode code)
 
   case dliteOSError:                return "Error calling a system function";
   case dliteKeyError:               return "Mapping key is not found";
+  case dliteSearchError:            return "Error in search";
   case dliteParseError:             return "Cannot parse input";
   case dliteSerialiseError:         return "Cannot serialise output";
   case dliteUnsupportedError:       return "Feature is not implemented/supported";
diff --git a/src/dlite-errors.h b/src/dlite-errors.h
index 773013c1b..d8a0f57b0 100644
--- a/src/dlite-errors.h
+++ b/src/dlite-errors.h
@@ -23,23 +23,24 @@ typedef enum {
   /* Additional DLite-specific errors */
   dliteOSError=-14,              /*!< Error calling a system function */
   dliteKeyError=-15,             /*!< Mapping key not found */
-  dliteParseError=-16,           /*!< Cannot parse input */
-  dliteSerialiseError=-17,       /*!< Cannot serialise output */
-  dliteUnsupportedError=-18,     /*!< Feature is not implemented/supported  */
-  dliteVerifyError=-19,          /*!< Object cannot be verified */
-  dliteInconsistentDataError=-20,/*!< Inconsistent data */
-  dliteStorageOpenError=-21,     /*!< Cannot open storage plugin */
-  dliteStorageLoadError=-22,     /*!< Cannot load storage plugin */
-  dliteStorageSaveError=-23,     /*!< Cannot save storage plugin */
-  dliteOptionError=-24,          /*!< Invalid storage plugin option */
-  dliteMissingInstanceError=-25, /*!< No instance with given id can be found */
-  dliteMissingMetadataError=-26, /*!< No metadata with given id can be found */
-  dliteMetadataExistError=-27,   /*!< Metadata with given id already exists */
-  dliteMappingError=-28,         /*!< Error in instance mappings */
-  dlitePythonError=-29,          /*!< Error calling Python API */
+  dliteSearchError=-16,          /*!< Error in search */
+  dliteParseError=-17,           /*!< Cannot parse input */
+  dliteSerialiseError=-18,       /*!< Cannot serialise output */
+  dliteUnsupportedError=-19,     /*!< Feature is not implemented/supported  */
+  dliteVerifyError=-20,          /*!< Object cannot be verified */
+  dliteInconsistentDataError=-21,/*!< Inconsistent data */
+  dliteStorageOpenError=-22,     /*!< Cannot open storage plugin */
+  dliteStorageLoadError=-23,     /*!< Cannot load storage plugin */
+  dliteStorageSaveError=-24,     /*!< Cannot save storage plugin */
+  dliteOptionError=-25,          /*!< Invalid storage plugin option */
+  dliteMissingInstanceError=-26, /*!< No instance with given id can be found */
+  dliteMissingMetadataError=-27, /*!< No metadata with given id can be found */
+  dliteMetadataExistError=-28,   /*!< Metadata with given id already exists */
+  dliteMappingError=-29,         /*!< Error in instance mappings */
+  dlitePythonError=-30,          /*!< Error calling Python API */
 
   /* Should always be the last error */
-  dliteLastError=-30
+  dliteLastError=-31
 } DLiteErrCode;
 
 
diff --git a/src/dlite-misc.c b/src/dlite-misc.c
index 77ab8c078..ffb67a389 100644
--- a/src/dlite-misc.c
+++ b/src/dlite-misc.c
@@ -460,44 +460,44 @@ int dlite_add_dll_path(void)
 
 #define ERR_STATE_ID "err-globals-id"
 #define ERR_MASK_ID "err-ignored-id"
-#define LOCALS_ID "dlite-misc-locals-id"
 
 
-/* A cache pointing to the current session handler */
-static DLiteGlobals *_globals_handler=NULL;
-
 /* Local state for this module. */
 typedef struct {
   int in_atexit;
 } Locals;
 
 
+/* A cache pointing to the current session handler */
+static DLiteGlobals *_globals_handler=NULL;
+
+/* Pointer to local state */
+Locals *_locals=NULL;
+
 /* Free variables in local state. */
-static void free_locals(void *locals)
+static void free_locals()
 {
-  UNUSED(locals);
+  _locals = NULL;
 }
 
 /* Return a pointer to local state. */
 static Locals *get_locals(void)
 {
-  static Locals *locals=NULL;
-  if (!locals) {
-    locals = dlite_globals_get_state(LOCALS_ID);
-    if (!locals) {
-      static Locals _locals;
-      locals = &_locals;
-      memset(locals, 0, sizeof(Locals));
-      dlite_globals_add_state(LOCALS_ID, locals, free_locals);
-    }
+  if (!_locals) {
+    static Locals locals;
+    _locals = &locals;
+    memset(_locals, 0, sizeof(Locals));
   }
-  return locals;
+  return _locals;
 }
 
 
 /* Called by atexit(). */
 static void _handle_atexit(void) {
 
+  /* No extra finalisation is needed if we already are in an atexit handler */
+  if (dlite_globals_in_atexit()) return;
+
   /* Mark that we are in an atexit handler */
   dlite_globals_set_atexit();
 
@@ -512,11 +512,12 @@ DLiteGlobals *dlite_globals_get(void)
   if (!_globals_handler) {
     _globals_handler = session_get_default();
 
+    dlite_init();
+
     /* Make valgrind and other memory leak detectors happy by freeing
        up all globals at exit. */
-    atexit(_handle_atexit);
-
-    dlite_init();
+    if (!dlite_globals_in_atexit())
+      atexit(_handle_atexit);
   }
   return _globals_handler;
 }
@@ -583,13 +584,16 @@ void dlite_finalize(void)
   Session *s = session_get_default();
 
   /* Don't free anything if we are in an atexit handler */
-  if (dlite_globals_in_atexit() || !getenv("DLITE_ATEXIT_FREE")) return;
+  if (dlite_globals_in_atexit() && !getenv("DLITE_ATEXIT_FREE")) return;
 
   /* Reset error handling */
   err_set_handler(NULL);
   err_set_nameconv(NULL);
 
   session_free(s);
+  _globals_handler = NULL;
+
+  free_locals();
 }
 
 
diff --git a/src/dlite-type.c b/src/dlite-type.c
index a8b72e8b0..da4c3530e 100644
--- a/src/dlite-type.c
+++ b/src/dlite-type.c
@@ -670,6 +670,7 @@ void *dlite_type_clear(void *p, DLiteType dtype, size_t size)
     if (((DLiteRelation *)p)->s)  free(((DLiteRelation *)p)->s);
     if (((DLiteRelation *)p)->p)  free(((DLiteRelation *)p)->p);
     if (((DLiteRelation *)p)->o)  free(((DLiteRelation *)p)->o);
+    if (((DLiteRelation *)p)->d)  free(((DLiteRelation *)p)->d);
     if (((DLiteRelation *)p)->id) free(((DLiteRelation *)p)->id);
     break;
   }
@@ -868,6 +869,10 @@ int dlite_type_print(char *dest, size_t n, const void *p, DLiteType dtype,
       m += strquote(dest+m, PDIFF(n, m), r->p);
       m += snprintf(dest+m, PDIFF(n, m), ", ");
       m += strquote(dest+m, PDIFF(n, m), r->o);
+      if (r->d) {
+        m += snprintf(dest+m, PDIFF(n, m), ", ");
+        m += strquote(dest+m, PDIFF(n, m), r->d);
+      }
       m += snprintf(dest+m, PDIFF(n, m), "]");
     }
     break;
@@ -920,7 +925,7 @@ int dlite_type_aprint(char **dest, size_t *n, size_t pos, const void *p,
 /* Maximum number of jsmn tokens in a dimension, property and relation */
 #define MAX_DIMENSION_TOKENS  5
 #define MAX_PROPERTY_TOKENS  64  // this supports at least 50 dimensions...
-#define MAX_RELATION_TOKENS   9
+#define MAX_RELATION_TOKENS  11
 
 /* Macro used by dlite_type_scan() to asign `target` when scanning a
    relation. */
@@ -928,7 +933,7 @@ int dlite_type_aprint(char **dest, size_t *n, size_t pos, const void *p,
   if (strnput_unquote(&buf, &bufsize, 0, src + t->start,                \
                       t->end - t->start, NULL, strquoteNoQuote) < 0)    \
     return -1;                                                          \
-  target = strndup(buf, t->end - t->start);
+  target = (buf[0]) ? strndup(buf, t->end - t->start) : NULL;
 
 
 /*
@@ -1208,8 +1213,8 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
       if ((r = jsmn_parse(&parser, src, len, tokens, MAX_RELATION_TOKENS)) < 0)
         return err(dliteParseError, "cannot parse relation: %s: '%s'",
                    jsmn_strerror(r), src);
-      if (tokens->size < 3 || tokens->size > 4)
-        return errx(dliteParseError, "relation should have 3 (optionally 4) elements");
+      if (tokens->size < 3 || tokens->size > 5)
+        return errx(dliteParseError, "relation should have 3 (optionally 5) elements");
       m = tokens->end - tokens->start;
       if (tokens->type == JSMN_ARRAY) {
         size_t bufsize=0;
@@ -1220,8 +1225,13 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
         SET_RELATION(rel->p, buf, bufsize, t, src);
         if (!(t = jsmn_element(src, tokens, 2))) return -1;
         SET_RELATION(rel->o, buf, bufsize, t, src);
-        if (tokens->size > 3 && (t = jsmn_element(src, tokens, 3)))
-          rel->id = strndup(src + t->start, t->end - t->start);
+        if (tokens->size > 3 && (t = jsmn_element(src, tokens, 3))) {
+          SET_RELATION(rel->d, buf, bufsize, t, src);
+        }
+        if (tokens->size > 4 && (t = jsmn_element(src, tokens, 4))) {
+          rel->id = (t->end > t->start) ?
+            strndup(src + t->start, t->end - t->start) : NULL;
+        }
         free(buf);
       } else if (tokens->type == JSMN_OBJECT) {
         size_t bufsize=0;
@@ -1232,8 +1242,12 @@ int dlite_type_scan(const char *src, int len, void *p, DLiteType dtype,
         SET_RELATION(rel->p, buf, bufsize, t, src);
         if (!(t = jsmn_item(src, tokens, "o"))) return -1;
         SET_RELATION(rel->o, buf, bufsize, t, src);
+        if ((t = jsmn_item(src, tokens, "d"))) {
+          SET_RELATION(rel->d, buf, bufsize, t, src);
+        }
         if ((t = jsmn_item(src, tokens, "id")))
-          rel->id = strndup(src + t->start, t->end - t->start);
+          rel->id = (t->end > t->start) ?
+            strndup(src + t->start, t->end - t->start) : NULL;
         free(buf);
       } else {
         return errx(dliteValueError, "relation should be a JSON array");
diff --git a/src/triple.c b/src/triple.c
index f2e3c64ef..691470794 100644
--- a/src/triple.c
+++ b/src/triple.c
@@ -58,9 +58,10 @@ const char *triple_get_default_namespace(void)
 */
 void triple_clean(Triple *t)
 {
-  free(t->s);
-  free(t->p);
-  free(t->o);
+  if (t->s) free(t->s);
+  if (t->p) free(t->p);
+  if (t->o) free(t->o);
+  if (t->d) free(t->d);
   if (t->id) free(t->id);
   memset(t, 0, sizeof(Triple));
 }
@@ -71,12 +72,13 @@ void triple_clean(Triple *t)
   NULL, a new id will be generated bases on `s`, `p` and `o`.
  */
 int triple_set(Triple *t, const char *s, const char *p, const char *o,
-                const char *id)
+               const char *d, const char *id)
 {
   t->s = strdup((s) ? s : "");
   t->p = strdup((p) ? p : "");
   t->o = strdup((o) ? o : "");
-  t->id = (id) ? strdup(id) : triple_get_id(NULL, s, p, o);
+  t->d = (d) ? strdup(d) : NULL;
+  t->id = (id) ? strdup(id) : triple_get_id(NULL, s, p, o, d);
   return 0;
 }
 
@@ -85,13 +87,10 @@ int triple_set(Triple *t, const char *s, const char *p, const char *o,
   it.  Don't use this function if `t` has not been initiated.
  */
 int triple_reset(Triple *t, const char *s, const char *p, const char *o,
-                const char *id)
+                 const char *d, const char *id)
 {
-  if (t->s)  free(t->s);
-  if (t->p)  free(t->p);
-  if (t->o)  free(t->o);
-  if (t->id) free(t->id);
-  return triple_set(t, s, p, o, id);
+  triple_clean(t);
+  return triple_set(t, s, p, o, d, id);
 }
 
 /*
@@ -99,7 +98,7 @@ int triple_reset(Triple *t, const char *s, const char *p, const char *o,
   Returns NULL on error, for instance if any of `s`, `p` or `o` are NULL.
 */
 char *triple_get_id(const char *namespace, const char *s, const char *p,
-                      const char *o)
+                    const char *o, const char *d)
 {
   SHA1_CTX context;
   unsigned char digest[20];
@@ -111,6 +110,7 @@ char *triple_get_id(const char *namespace, const char *s, const char *p,
   SHA1Update(&context, (unsigned char *)s, strlen(s));
   SHA1Update(&context, (unsigned char *)p, strlen(p));
   SHA1Update(&context, (unsigned char *)o, strlen(o));
+  if (d) SHA1Update(&context, (unsigned char *)d, strlen(d));
   SHA1Final(digest, &context);
   if (!namespace) namespace = triple_get_default_namespace();
   if (namespace) size += strlen(namespace);
@@ -123,19 +123,20 @@ char *triple_get_id(const char *namespace, const char *s, const char *p,
 
 /*
   Copies triple `src` to `dest` and returns a pointer to `dest`.
+
+  Existing memory hold by `dest` is not free'ed.  So if `dest` may
+  hold some memory, call `triple_clean()` before calling this
+  function.
  */
 Triple *triple_copy(Triple *dest, const Triple *src)
 {
   assert(src);
   assert(dest);
-  if (dest->s) free(dest->s);
-  if (dest->p) free(dest->p);
-  if (dest->o) free(dest->o);
-  if (dest->id) free(dest->id);
   memset(dest, 0, sizeof(Triple));
   if (src->s  && !(dest->s = strdup(src->s)))   goto fail;
   if (src->p  && !(dest->p = strdup(src->p)))   goto fail;
   if (src->o  && !(dest->o = strdup(src->o)))   goto fail;
+  if (src->d  && !(dest->d = strdup(src->d)))   goto fail;
   if (src->id && !(dest->id = strdup(src->id))) goto fail;
   return dest;
  fail:
diff --git a/src/triple.h b/src/triple.h
index 77fcede54..24809cd10 100644
--- a/src/triple.h
+++ b/src/triple.h
@@ -21,6 +21,7 @@ typedef struct _Triple {
   char *s;     /*!< subject */
   char *p;     /*!< predicate */
   char *o;     /*!< object */
+  char *d;     /*!< datatype */
   char *id;    /*!< unique ID identifying this triple */
 } Triple;
 
@@ -46,16 +47,17 @@ void triple_clean(Triple *t);
   Convinient function to assign a triple. This allocates new memory
   for the internal s, p, o and id pointers.  If `id` is
   NULL, a new id will be generated bases on `s`, `p` and `o`.
+  For literals, the datatype can be provided with `d`.
  */
 int triple_set(Triple *t, const char *s, const char *p, const char *o,
-                const char *id);
+               const char *d, const char *id);
 
 /**
   Like triple_set(), but free's allocated memory in `t` before re-assigning
   it.  Don't use this function if `t` has not been initiated.
  */
 int triple_reset(Triple *t, const char *s, const char *p, const char *o,
-                  const char *id);
+                 const char *d, const char *id);
 
 /**
   Returns an newly malloc'ed unique id calculated from triple.
@@ -66,11 +68,15 @@ int triple_reset(Triple *t, const char *s, const char *p, const char *o,
   Returns NULL on error.
 */
 char *triple_get_id(const char *namespace, const char *s, const char *p,
-                      const char *o);
+                    const char *o, const char *d);
 
 /**
   Copies triple `src` to `dest` and returns a pointer to `dest`.
- */
+
+  Existing memory hold by `dest` is not free'ed.  So if `dest` may
+  hold some memory, call `triple_clean()` before calling this
+  function.
+*/
 Triple *triple_copy(Triple *dest, const Triple *src);
 
 
diff --git a/src/triplestore-builtin.c b/src/triplestore-builtin.c
index ea80d2255..455be0baf 100644
--- a/src/triplestore-builtin.c
+++ b/src/triplestore-builtin.c
@@ -34,8 +34,8 @@
 
 
 
-/* Allocate triplestore memory in chunks of TRIPLESTORE_BUFFSIZE */
-#define TRIPLESTORE_BUFFSIZE 1024
+/* Allocate triplestore memory in chunks of TRIPLESTORE_CHUNKSIZE */
+#define TRIPLESTORE_CHUNKSIZE 1024
 
 
 /* Prototype for cleanup-function */
@@ -43,7 +43,7 @@ typedef void (*Freer)(void *ptr);
 
 /* Triple store. */
 struct _TripleStore {
-  Triple *triples;  /*!< array of triples */
+  Triple *triples;    /*!< array of triples */
   size_t length;      /*!< logically number of triples (excluding pending
                            removes */
   size_t true_length; /*!< number of triples (including pending removes) */
@@ -54,6 +54,7 @@ struct _TripleStore {
   size_t niter;       /*!< counter for number of running iterators */
   int freed;          /*!< set to non-zero when this store is supposed to
                            be freed, but kept alive due to existing iterators */
+  const char *ns;     /*!< Namespace */
 };
 
 
@@ -104,28 +105,17 @@ size_t triplestore_length(TripleStore *ts)
 }
 
 
-///* Compare triples (in s-o-p order) */
-//static int compar(const void *p1, const void *p2)
-//{
-//  int v;
-//  Triple *t1 = (Triple *)p1;
-//  Triple *t2 = (Triple *)p2;
-//  if ((v = strcmp(t1->s, t2->s))) return v;
-//  if ((v = strcmp(t1->o, t2->o))) return v;
-//  return strcmp(t1->p, t2->p);
-//}
-
-
 /*
   Adds a single triple to store.  Returns non-zero on error.
  */
 int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
+                    const char *o, const char *d)
 {
   Triple t;
   t.s = (char *)s;
   t.p = (char *)p;
   t.o = (char *)o;
+  t.d = (char *)d;
   t.id = NULL;
   return triplestore_add_triples(ts, &t, 1);
 }
@@ -141,8 +131,8 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples,
 
   /* make space for new triples */
   if (ts->size < ts->true_length + n) {
-    size_t m = (ts->true_length + n - ts->size) / TRIPLESTORE_BUFFSIZE;
-    size_t size = ts->size + (m + 1) * TRIPLESTORE_BUFFSIZE;
+    size_t m = (ts->true_length + n - ts->size) / TRIPLESTORE_CHUNKSIZE;
+    size_t size = ts->size + (m + 1) * TRIPLESTORE_CHUNKSIZE;
     void *ptr;
     assert(size >= ts->true_length + n);
     if (!(ptr = realloc(ts->triples, size * sizeof(Triple))))
@@ -160,14 +150,16 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples,
     if (triples[i].id) {
       if (!(id = strdup(triples[i].id))) return err(1, "allocation error");
     } else {
-      if (!(id = triple_get_id(NULL, triples[i].s,
-                                triples[i].p, triples[i].o)))
+      if (!(id = triple_get_id(NULL, triples[i].s, triples[i].p,
+                               triples[i].o, triples[i].d)))
+
         return 1;
     }
     if (!map_get(&ts->map, id)) {
       if (!(t->s = strdup(triples[i].s))) return err(1, "allocation error");
       if (!(t->p = strdup(triples[i].p))) return err(1, "allocation error");
       if (!(t->o = strdup(triples[i].o))) return err(1, "allocation error");
+      t->d = (triples[i].d) ? strdup(triples[i].d) : NULL;
       t->id = id;
       ts->length++;
       ts->true_length++;
@@ -208,6 +200,7 @@ static int _remove_by_index(TripleStore *ts, size_t n)
   return 0;
 }
 
+
 /*
   Removes a triple identified by it's `id`.  Returns non-zero on
   error or if no such triple can be found.
@@ -225,8 +218,8 @@ int triplestore_remove_by_id(TripleStore *ts, const char *id)
   be NULL, allowing for multiple matches.  Returns the number of
   triples removed.
 */
-int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o)
+int triplestore_remove(TripleStore *ts, const char *s, const char *p,
+                       const char *o, const char *d)
 {
   int i=ts->true_length, n=0;
   while (--i >= 0) {
@@ -234,7 +227,8 @@ int triplestore_remove(TripleStore *ts, const char *s,
     if (!t->id) continue;
     if ((!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0)) {
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (t->d && strcmp(d, t->d) == 0))) {
       if (_remove_by_index(ts, i) == 0) n++;
     }
   }
@@ -256,6 +250,7 @@ void triplestore_clear(TripleStore *ts)
   map_deinit(&ts->map);
   memset(ts, 0, sizeof(TripleStore));
   ts->niter = niter;
+  if (ts->ns) free((char *)ts->ns);
 }
 
 
@@ -278,7 +273,8 @@ const Triple *triplestore_get(const TripleStore *ts, const char *id)
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
 const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                                      const char *p, const char *o)
+                                     const char *p, const char *o,
+                                     const char *d)
 {
   size_t i;
   for (i=0; i<ts->true_length; i++) {
@@ -286,7 +282,8 @@ const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
     if (t->id &&
         (!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0))
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (t->d && strcmp(d, t->d) == 0)))
       return t;
   }
   return NULL;
@@ -334,8 +331,8 @@ void triplestore_deinit_state(TripleState *state)
       }
     }
     assert(ts->true_length == ts->length);
-    if (ts->size > ts->length + TRIPLESTORE_BUFFSIZE) {
-      ts->size = ts->length + ts->length % TRIPLESTORE_BUFFSIZE;
+    if (ts->size > ts->length + TRIPLESTORE_CHUNKSIZE) {
+      ts->size = ts->length + ts->length % TRIPLESTORE_CHUNKSIZE;
       ts->triples = realloc(ts->triples, ts->size*sizeof(Triple));
     }
   }
@@ -382,15 +379,28 @@ const Triple *triplestore_poll(TripleState *state)
 
 
 /*
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
+
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
+
+  if `d` is NUL (empty string), it will match non-literal objects.
+
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
  */
 const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o)
+                               const char *s, const char *p, const char *o,
+                               const char *d)
 {
   TripleStore *ts = state->ts;
   while (state->pos < ts->true_length) {
@@ -398,20 +408,9 @@ const Triple *triplestore_find(TripleState *state,
     if (t->id &&
         (!s || strcmp(s, t->s) == 0) &&
         (!p || strcmp(p, t->p) == 0) &&
-        (!o || strcmp(o, t->o) == 0))
+        (!o || strcmp(o, t->o) == 0) &&
+        (!d || (!d[0] && !t->d) || (t->d && strcmp(d, t->d) == 0)))
       return t;
   }
   return NULL;
 }
-
-/*
-  Default implementation...
- */
-const Triple *triplestore_find2(TripleState *state,
-                                const char *s, const char *p, const char *o,
-                                int literal, const char *lang)
-{
-  UNUSED(literal);
-  UNUSED(lang);
-  return triplestore_find(state, s, p, o);
-}
diff --git a/src/triplestore-redland.c b/src/triplestore-redland.c
index a316924c9..f23b5a2f4 100644
--- a/src/triplestore-redland.c
+++ b/src/triplestore-redland.c
@@ -122,50 +122,118 @@ static void finalize_check()
 }
 
 
+/* Create context for stream filter. */
+typedef struct { const char *o, *d; } StreamContext;
+
+static void *stream_get_context(const char *o, const char *d)
+{
+  StreamContext *c;
+  if (!(c = calloc(1, sizeof(StreamContext))))
+    return err(dliteMemoryError, "allocation failure"), NULL;
+  c->o = o;
+  c->d = d;
+  return (void *)c;
+}
+
+/* Free context for stream filter. */
+static void stream_free(void *context)
+{
+  free(context);
+}
+
+/* Filter for the stream returned by find. Used to handle object and
+   datatype. */
+static librdf_statement *stream_map(librdf_stream *stream, void *context,
+                                    librdf_statement *item)
+{
+  UNUSED(stream);
+  librdf_node *no = librdf_statement_get_object(item);
+  StreamContext *c = (StreamContext *)context;
+
+  if (librdf_node_is_literal(no)) {
+    if (c->o) {
+      char *value = (char *)librdf_node_get_literal_value(no);
+      if (strcmp(value, c->o) != 0) return NULL;
+    }
+    if (c->d) {
+      char *lang = librdf_node_get_literal_value_language(no);
+      librdf_uri *uri = librdf_node_get_literal_value_datatype_uri(no);
+      if (lang) {
+        if ((c->d[0] != '@') || (strcmp(c->d+1, lang) != 0)) return NULL;
+      }
+      if (uri) {
+        if (strcmp(c->d, (char *)librdf_uri_as_string(uri)) != 0) return NULL;
+      }
+      if (c->d[0] == '\0' && (lang || uri)) return NULL;
+    }
+  } else {
+    librdf_uri *iri = librdf_node_get_uri(no);
+    if (c->d && c->d[0]) return NULL;
+    if (c->o && iri && strcmp(c->o, (char *)librdf_uri_as_string(iri)) != 0)
+      return NULL;
+  }
+  return item;
+}
+
+
+
 /*
   Returns a new librdf stream with all triples matching `s`, `p` and
   `o`.  Any of these may be NULL, allowing for multiple matches.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  If `o` is non-NULL and `d` is NULL, the object is first assumed to
+  be an IRI. If there are no matches, the object is assumed to be a
+  literal with no specified language or type.
 
   Returns NULL on error.
 */
 static librdf_stream *find(TripleStore *ts, const char *s, const char *p,
-                           const char *o, int literal, const char *lang)
+                           const char *o, const char *d)
 {
   librdf_node *ns=NULL, *np=NULL, *no=NULL;
+  librdf_uri *dt=NULL;
   librdf_statement *statement=NULL;
   librdf_stream *stream=NULL;
   int failed = 1;
+
   if (s &&!(ns = librdf_new_node_from_uri_string(ts->world,
                                                  (const unsigned char *)s)))
     FAIL1("error creating node for subject: '%s'", s);
   if (p && !(np = librdf_new_node_from_uri_string(ts->world,
                                                   (const unsigned char *)p)))
     FAIL1("error creating node for predicate: '%s'", p);
-  if (literal) {
-    if (o && !(no = librdf_new_node_from_literal(ts->world,
-                                                 (const unsigned char *)o,
-                                                 lang, 0)))
-      FAIL1("error creating node for literal object: '%s'", o);
-  } else {
-    if (o && !(no = librdf_new_node_from_uri_string(ts->world,
-                                                    (const unsigned char *)o)))
-      FAIL1("error creating node for object: '%s'", o);
+
+  if (o && d && *d) {
+    const char *lang=NULL;
+    if (d[0] == '@')
+      lang = d+1;
+    else if (!(dt = librdf_new_uri(ts->world, (const unsigned char *)d)))
+      FAIL1("error datatype '%s'", d);
+    if (!(no = librdf_new_node_from_typed_literal(ts->world,
+                                                  (const unsigned char *)o,
+                                                  lang, dt)))
+      FAIL2("error creating node for literal object: '%s' of type '%s'", o, d);
   }
+
   if (!(statement = librdf_new_statement_from_nodes(ts->world, ns, np, no))) {
-    ns = np = no = NULL;
-    FAIL3("error creating statement: (%s, %s, %s)", s, p, o);
+    ns = np = no = NULL;  // now owned by statement
+    FAIL4("error creating statement: (%s, %s, %s) (d=%s)", s, p, o, d);
   }
   if (!(stream = librdf_model_find_statements(ts->model, statement)))
-    FAIL3("error finding statements matching (%s, %s, %s)", s, p, o);
+    FAIL4("error finding statements matching (%s, %s, %s) (d=%s)", s, p, o, d);
+
+  if ((o || d) && !(o && d && *d)) {
+    void *context = stream_get_context(o, d);
+    if (librdf_stream_add_map(stream, stream_map, stream_free, context))
+      FAIL("error adding mapping function to stream");
+  }
 
   failed = 0;
  fail:
+  if (failed) {
+    if (stream) librdf_free_stream(stream);
+    stream = NULL;
+  }
   if (statement) {
     librdf_free_statement(statement);
   } else if (failed) {
@@ -173,10 +241,7 @@ static librdf_stream *find(TripleStore *ts, const char *s, const char *p,
     if (np) librdf_free_node(np);
     if (no) librdf_free_node(no);
   }
-  if (failed) {
-    if (stream) librdf_free_stream(stream);
-    stream = NULL;
-  }
+  if (dt) librdf_free_uri(dt);
   return stream;
 }
 
@@ -194,20 +259,33 @@ static int assign_triple_from_statement(Triple *t,
                                          librdf_statement *statement)
 {
   librdf_node *node;
-  unsigned char *s=NULL, *p=NULL, *o=NULL;
+  unsigned char *s=NULL, *p=NULL, *o=NULL, *d=NULL;
+  char *lang;
+  librdf_uri *datatype;
   errno = 0;
   s = librdf_uri_to_string(librdf_node_get_uri(librdf_statement_get_subject(statement)));
   p = librdf_uri_to_string(librdf_node_get_uri(librdf_statement_get_predicate(statement)));
   node = librdf_statement_get_object(statement);
   switch (librdf_node_get_type(node)) {
   case LIBRDF_NODE_TYPE_UNKNOWN:
-    return err(1, "unknown node type");
+    FAIL("unknown node type");
   case LIBRDF_NODE_TYPE_RESOURCE:
     o = librdf_uri_to_string(librdf_node_get_uri(node));
     break;
   case LIBRDF_NODE_TYPE_LITERAL:
     o = librdf_node_get_literal_value(node);
     if (o) o = (unsigned char *)strdup((char *)o);
+
+    if ((datatype = librdf_node_get_literal_value_datatype_uri(node))) {
+      if (!(d = librdf_uri_to_string(datatype)))
+        FAIL("cannot convert datatype URI to string");
+    } else if ((lang = librdf_node_get_literal_value_language(node))) {
+      size_t n = strlen(lang);
+      if (!(d = calloc(1, n+2)))
+        FAILCODE(dliteMemoryError, "allocation failure");
+      d[0] = '@';
+      strncpy((char *)d+1, lang, n+1);
+    }
     break;
   case LIBRDF_NODE_TYPE_BLANK:
     o = librdf_node_get_blank_identifier(node);
@@ -215,15 +293,21 @@ static int assign_triple_from_statement(Triple *t,
     break;
   }
   if (s && p && o) {
-    if (t->s) free(t->s);
-    if (t->p) free(t->p);
-    if (t->o) free(t->o);
+    triple_clean(t);
     t->s = (char *)s;
     t->p = (char *)p;
     t->o = (char *)o;
+    t->d = (char *)d;
+    t->id = NULL;
     return 0;
   }
-  return err(1, "error in assign_triple_from_statement");
+  FAIL("error in assign_triple_from_statement");
+ fail:
+  if (s) free(s);
+  if (p) free(p);
+  if (o) free(o);
+  if (d) free(d);
+  return 1;
 }
 
 
@@ -301,20 +385,6 @@ int triplestore_set_default_storage(const char *name)
   return err(1, "no such triplestore storage: %s", name);
 }
 
-/* Set default namespace */
-void triplestore_set_namespace(TripleStore *ts, const char *ns)
-{
-  if (ts->ns) free((char *)ts->ns);
-  ts->ns = (ns) ? strdup(ns) : NULL;
-}
-
-/* Returns a pointer to default namespace.
-   It may be NULL if it hasn't been set */
-const char *triplestore_get_namespace(TripleStore *ts)
-{
-  return ts->ns;
-}
-
 /* Returns a pointer to the name of default storage or NULL on error. */
 const char *triplestore_get_default_storage()
 {
@@ -404,9 +474,7 @@ void triplestore_free(TripleStore *ts)
   if (ts->name)          free((char *)ts->name);
   if (ts->options)       free((char *)ts->options);
   if (ts->ns)            free((char *)ts->ns);
-  if (ts->triple.s)      free(ts->triple.s);
-  if (ts->triple.p)      free(ts->triple.p);
-  if (ts->triple.o)      free(ts->triple.o);
+  triple_clean(&ts->triple);
   free(ts);
   finalize_check();
 }
@@ -445,23 +513,15 @@ static librdf_node *new_uri_node(TripleStore *ts, const char *uri)
   return node;
 }
 
-/*
-  Adds a single (s,p,o) triple to store.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
 
-  If `datatype_uri` is not NULL, it should be an uri for the literal
-  datatype. Ex: "xsd:integer".
+/*
+  Adds a single (s,p,o,d) triple (of URIs) to store.  If datatype `d` is NULL,
+  is the object is considered to be an IRI. Otherwise it is a literal.
 
   Returns non-zero on error.
  */
-int triplestore_add2(TripleStore *ts, const char *s, const char *p,
-                     const char *o, int literal, const char *lang,
-                     const char *datatype_uri)
+int triplestore_add(TripleStore *ts, const char *s, const char *p,
+                    const char *o, const char *d)
 {
   librdf_node *ns=NULL, *np=NULL, *no=NULL;
   librdf_uri *uri=NULL;
@@ -469,63 +529,34 @@ int triplestore_add2(TripleStore *ts, const char *s, const char *p,
     FAIL1("error creating node for subject: '%s'", s);
   if (!(np = new_uri_node(ts, p)))
     FAIL1("error creating node for predicate: '%s'", p);
-  if (literal) {
-    if (datatype_uri &&
-        !(uri = librdf_new_uri(ts->world, (unsigned char *)datatype_uri)))
-      FAIL1("error creating uri from '%s'", datatype_uri);
+
+  if (d && d[0] == '@') {
+    if (!(no = librdf_new_node_from_typed_literal(ts->world, (unsigned char *)o,
+                                                  d+1, NULL)))
+      FAIL2("error creating language-tagged (%s) node for object: '%s'", d, o);
+  } else if (d) {
+    if (!(uri = librdf_new_uri(ts->world, (unsigned char *)d)))
+      FAIL1("error creating datatype URI from: '%s'", d);
     if (!(no = librdf_new_node_from_typed_literal(ts->world, (unsigned char *)o,
-                                                  lang, uri)))
-      FAIL1("error creating node for object: '%s'", o);
+                                                  NULL, uri)))
+      FAIL2("error creating typed (%s) literal node for object: '%s'", d, o);
   } else {
     if (!(no = new_uri_node(ts, o)))
-      FAIL1("error creating node for object: '%s'", o);
+      FAIL1("error creating IRI node for object: '%s'", o);
   }
   if (librdf_model_add(ts->model, ns, np, no))
-    FAIL("error adding triple");
+    FAIL4("error adding triple (%s, %s, %s, %s)", s, p, o, d);
   if (uri) librdf_free_uri(uri);
   return 0;
  fail:
-  if (uri) librdf_free_uri(uri);
   if (ns) librdf_free_node(ns);
   if (np) librdf_free_node(np);
   if (no) librdf_free_node(no);
+  if (uri) librdf_free_uri(uri);
   return 1;
 }
 
 
-/*
-  Adds a single (s,p,o) triple (of URIs) to store.  The object is
-  considered to be a literal with no language.
-
-  Returns non-zero on error.
- */
-int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 1, NULL, NULL);
-}
-
-/*
-  Adds a single triple to store.  The object is considered to be an
-  english literal.  Returns non-zero on error.
- */
-int triplestore_add_en(TripleStore *ts, const char *s, const char *p,
-                    const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 1, "en", NULL);
-}
-
-/*
-  Adds a single triple to store.  The object is considered to be an URI.
-  Returns non-zero on error.
- */
-int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
-                        const char *o)
-{
-  return triplestore_add2(ts, s, p, o, 0, NULL, NULL);
-}
-
-
 /*
   Adds `n` triples to store.  Returns non-zero on error.
  */
@@ -534,7 +565,7 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n)
   size_t i;
   for (i=0; i<n; i++) {
     const Triple *t = triples + i;
-    if (triplestore_add(ts, t->s, t->p, t->o)) return 1;
+    if (triplestore_add(ts, t->s, t->p, t->o, t->d)) return 1;
   }
   return 0;
 }
@@ -542,22 +573,17 @@ int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n)
 
 /*
   Removes a triple identified by `s`, `p` and `o`.  Any of these may
-  be NULL, allowing for multiple matches.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
-
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  be NULL, allowing for multiple matches.  The object is assumed to
+  be a literal with no language.
 
   Returns the number of triples removed or -1 on error.
 */
-int triplestore_remove2(TripleStore *ts, const char *s, const char *p,
-                        const char *o, int literal, const char *lang)
+int triplestore_remove(TripleStore *ts, const char *s,
+                       const char *p, const char *o, const char *d)
 {
   librdf_stream *stream;
   int failed=0, removed=0;
-  if (!(stream = find(ts, s, p, o, literal, lang))) return -1;
+  if (!(stream = find(ts, s, p, o, d))) return -1;
   do {
     librdf_statement *statement;
     if (!(statement = librdf_stream_get_object(stream))) break;
@@ -569,27 +595,13 @@ int triplestore_remove2(TripleStore *ts, const char *s, const char *p,
 }
 
 
-/*
-  Removes a triple identified by `s`, `p` and `o`.  Any of these may
-  be NULL, allowing for multiple matches.  The object is assumed to
-  be a literal with no language.
-
-  Returns the number of triples removed or -1 on error.
-*/
-int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o)
-{
-  return triplestore_remove2(ts, s, p, o, 1, NULL);
-}
-
-
 /*
   Removes all relations in triplestore and releases all references to
   external memory.
  */
 void triplestore_clear(TripleStore *ts)
 {
-  triplestore_remove(ts, NULL, NULL, NULL);
+  triplestore_remove(ts, NULL, NULL, NULL, NULL);
 }
 
 
@@ -677,30 +689,34 @@ const Triple *triplestore_poll(TripleState *state)
 
 
 /*
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  if `d` is NUL (empty string), it will match non-literal objects.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
 
-  NULL is also returned on error.
- */
-const Triple *triplestore_find2(TripleState *state,
-                                 const char *s, const char *p, const char *o,
-                                 int literal, const char *lang)
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
+*/
+const Triple *triplestore_find(TripleState *state,
+                               const char *s, const char *p, const char *o,
+                               const char *d)
 {
   librdf_stream *stream;
   librdf_statement *statement;
   Triple *t = &state->ts->triple;
   assert(t);
-  if (!state->data && !(state->data = find(state->ts, s, p, o, literal, lang)))
+  if (!state->data && !(state->data = find(state->ts, s, p, o, d)))
     return err(1, "cannot create model stream"), NULL;
   stream = (librdf_stream *)state->data;
   if (!(statement = librdf_stream_get_object(stream))) return NULL;
@@ -710,75 +726,18 @@ const Triple *triplestore_find2(TripleState *state,
 }
 
 
-/*
-   Equivalent to `triplestore_find2(state, s, p, o, 1, NULL)`.
-*/
-const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o)
-{
-  return triplestore_find2(state, s, p, o, 1, NULL);
-}
-
-
 /*
   Returns a pointer to first triple matching `s`, `p` and `o` or NULL
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
-const Triple *triplestore_find_first2(const TripleStore *ts, const char *s,
-                                       const char *p, const char *o,
-                                       int literal, const char *lang)
+const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
+                                     const char *p, const char *o,
+                                     const char *d)
 {
   TripleState state;
   const Triple *t;
   triplestore_init_state((TripleStore *)ts, &state);
-  t = triplestore_find2(&state, s, p, o, literal, lang);
+  t = triplestore_find(&state, s, p, o, d);
   triplestore_deinit_state(&state);
   return t;
 }
-
-
-/*
-  Returns a pointer to first triple matching `s`, `p` and `o` or NULL
-  if no match can be found.  Any of `s`, `p` or `o` may be NULL.
- */
-const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                                      const char *p, const char *o)
-{
-  return triplestore_find_first2(ts, s, p, o, 1, NULL);
-}
-
-
-#if 0
-
-
-/*
-  Removes a triple identified by it's `id`.  Returns non-zero on
-  error or if no such triple can be found.
-*/
-int triplestore_remove_by_id(TripleStore *ts, const char *id)
-{
-  int *n;
-  if (!(n = map_get(&ts->map, id)))
-    return err(1, "no such triple id: \"%s\"", id);
-  return _remove_by_index(ts, *n);
-}
-
-
-
-/*
-  Returns a pointer to triple with given id or NULL if no match can be found.
-*/
-const Triple *triplestore_get(const TripleStore *ts, const char *id)
-{
-  int *n = map_get(&((TripleStore *)ts)->map, id);
-  if (!n)
-    return errx(1, "no triple with id \"%s\"", id), NULL;
-  if (!ts->triples[*n].id)
-    return errx(1, "triple \"%s\" has been removed", id), NULL;
-  return ts->triples + *n;
-}
-
-
-
-
-#endif
diff --git a/src/triplestore.c b/src/triplestore.c
index d1fb4549b..93e6450be 100644
--- a/src/triplestore.c
+++ b/src/triplestore.c
@@ -6,4 +6,95 @@
 #include "triplestore-redland.c"
 #else
 #include "triplestore-builtin.c"
+#include "dlite-macros.h"
 #endif
+
+
+/*
+  Adds a single triple to store.  The object is considered to be an
+  english literal.  Returns non-zero on error.
+ */
+int triplestore_add_en(TripleStore *ts, const char *s, const char *p,
+                    const char *o)
+{
+  return triplestore_add(ts, s, p, o, "@en");
+}
+
+/*
+  Adds a single triple to store.  The object is considered to be an URI.
+  Returns non-zero on error.
+ */
+int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
+                        const char *o)
+{
+  return triplestore_add(ts, s, p, o, NULL);
+}
+
+/* Set default namespace */
+void triplestore_set_namespace(TripleStore *ts, const char *ns)
+{
+  if (ts->ns) free((char *)ts->ns);
+  ts->ns = (ns) ? strdup(ns) : NULL;
+}
+
+/* Returns a pointer to default namespace.
+   It may be NULL if it hasn't been set */
+const char *triplestore_get_namespace(TripleStore *ts)
+{
+  return ts->ns;
+}
+
+/*
+  Return pointer to the value for a pair of two criteria.
+
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the triplestore and should be copied by the user
+  since it may be overwritten by later calls to the triplestore.
+
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
+
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
+ */
+const char *triplestore_value(TripleStore *ts, const char *s, const char *p,
+                              const char *o, const char *d,
+                              const char *fallback, int any)
+{
+  int n=0, i=-1;
+  const char *value=NULL;
+  TripleState state;
+  const Triple *t;
+  triplestore_init_state(ts, &state);
+  if (s) n++; else i=0;
+  if (p) n++; else i=1;
+  if (o) n++; else i=2;
+  if (n != 2) FAILCODE3(dliteTypeError,
+                        "triplestore_value() expects exact two of "
+                        "s='%s', p='%s', o='%s' to be non-NULL", s, p, o);
+  assert(i >= 0);
+  if (!(t = triplestore_find(&state, s, p, o, d))) {
+    if (!fallback)
+      FAILCODE4(dliteSearchError, "no values matching the criteria: "
+                "s='%s', p='%s', o='%s', d='%s'", s, p, o, d);
+    value = fallback;
+  } else {
+    switch (i) {
+    case 0: value = t->s; break;
+    case 1: value = t->p; break;
+    case 2: value = t->o; break;
+    }
+  }
+  if (!any && triplestore_find(&state, s, p, o, d))
+    FAILCODE4(dliteSearchError, "more than one value matching the criteria: "
+              "s='%s', p='%s', o='%s', d='%s'.  Maybe you want to set `any` "
+              "to true?", s, p, o, d);
+  triplestore_deinit_state(&state);
+  return value;
+ fail:
+  triplestore_deinit_state(&state);
+  return NULL;
+}
diff --git a/src/triplestore.h b/src/triplestore.h
index aa9467a9c..51142cbf1 100644
--- a/src/triplestore.h
+++ b/src/triplestore.h
@@ -55,8 +55,24 @@ librdf_world *triplestore_get_world(TripleStore *ts);
 /** Returns the internal librdf model. */
 librdf_model *triplestore_get_model(TripleStore *ts);
 
-#endif
+/** Like triplestore_create_with_storage(), but also takes a librdf world
+    as argument. */
+TripleStore *triplestore_create_with_world(librdf_world *world,
+                                           const char *storage_name,
+                                           const char *name,
+                                           const char *options);
+
 
+/* ================================== */
+/* Builtin-specific functions         */
+/* ================================== */
+#else  /* !HAVE_REDLAND */
+
+/** Removes a triple identified by it's `id`.  Returns non-zero if no
+  such triple can be found. */
+int triplestore_remove_by_id(TripleStore *ts, const char *id);
+
+#endif
 
 
 /* ================================== */
@@ -71,6 +87,8 @@ TripleStore *triplestore_create();
 /**
   Returns a new empty triplestore.
 
+  Without librdf, this behaves like triplestore_create().
+
   Arguments:
     storage_name: Name of storage module. If NULL, the default storage will
                   be used.
@@ -93,7 +111,7 @@ void triplestore_free(TripleStore *ts);
 
 
 /**
-  Set default namespace
+  Set default namespace.
 */
 void triplestore_set_namespace(TripleStore *ts, const char *ns);
 
@@ -111,30 +129,19 @@ size_t triplestore_length(TripleStore *ts);
 
 
 /**
-  Adds a single (s,p,o) triple to store.
-
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  Adds triple to store.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  If `d` is NULL, the object is considered to be an IRI.
 
-  If `datatype_uri` is not NULL, it should be an uri for the literal
-  datatype. Ex: "xsd:integer".
+  If `d` starts with '@', the object is considered to be a
+  language-tagged plain text literal.  The language code should follow
+  directly after the '@' sign.  Ex: "@en".
 
-  Returns non-zero on error.
- */
-int triplestore_add2(TripleStore *ts, const char *s, const char *p,
-                     const char *o, int literal, const char *lang,
-                     const char *datatype_uri);
-
-
-/**
-  Adds a single triple to store.  The object is considered to be a
-  literal with no language.  Returns non-zero on error.
+  Otherwise, the object is considered to be a literal with datatype
+  specified by `d`.  Ex: "xsd:integer".
  */
 int triplestore_add(TripleStore *ts, const char *s, const char *p,
-                    const char *o);
+                    const char *o, const char *d);
 
 
 /**
@@ -159,20 +166,13 @@ int triplestore_add_uri(TripleStore *ts, const char *s, const char *p,
 int triplestore_add_triples(TripleStore *ts, const Triple *triples, size_t n);
 
 
-/**
-  Removes a triple identified by it's `id`.  Returns non-zero if no such
-  triple can be found.
-*/
-int triplestore_remove_by_id(TripleStore *ts, const char *id);
-
-
 /**
   Removes a triple identified by `s`, `p` and `o`.  Any of these may
   be NULL, allowing for multiple matches.  Returns the number of
   triples removed.
 */
 int triplestore_remove(TripleStore *ts, const char *s,
-                       const char *p, const char *o);
+                       const char *p, const char *o, const char *d);
 
 
 /**
@@ -192,7 +192,8 @@ const Triple *triplestore_get(const TripleStore *ts, const char *id);
   if no match can be found.  Any of `s`, `p` or `o` may be NULL.
  */
 const Triple *triplestore_find_first(const TripleStore *ts, const char *s,
-                             const char *p, const char *o);
+                                     const char *p, const char *o,
+                                     const char *d);
 
 
 /**
@@ -224,36 +225,49 @@ const Triple *triplestore_next(TripleState *state);
 const Triple *triplestore_poll(TripleState *state);
 
 /**
-  This function should be called iteratively.  Before the first call
-  it should be provided a `state` initialised with triplestore_init_state().
+  Return next triple matching s-p-o triple, where `d` is the datatype
+  of the object. Any of `s`, `p`, `o` or `d` may be NULL, in which case
+  they will match anything.
 
-  For each call it will return a pointer to triple matching `s`, `p`
-  and `o`.  Any of `s`, `p` or `o` may be NULL.  When no more matches
-  can be found, NULL is returned.
+  If `d` starts with '@', it will match language-tagged plain text
+  literal objects whos XML language abbreviation matches the string
+  following the '@'-sign.
 
-  No other calls to triplestore_add() or triplestore_find() should be
-  done while searching.
+  if `d` is NUL (empty string), it will match non-literal objects.
 
-  NULL is also returned on error.
+  Any other non-NULL `d` will match literal objects whos datatype are `d`.
+
+  When no more matches can be found, NULL is returned.  NULL is also
+  returned on error.
+
+  This function should be called iteratively.  Before the first call
+  it should be provided a `state` initialised with triplestore_init_state()
+  and deinitialised with triplestore_deinit_state().
  */
 const Triple *triplestore_find(TripleState *state,
-                                const char *s, const char *p, const char *o);
+                               const char *s, const char *p, const char *o,
+                               const char *d);
 
 
 /**
-  Like triplestore_find(), but has two additional arguments.
+  Return pointer to the value for a pair of two criteria.
 
-  If `literal` is non-zero the object will be considered to be a
-  literal, otherwise it is considered to be an URI.
+  Useful if one knows that there may only be one value.  The returned
+  value is held by the triplestore and should be copied by the user
+  since it may be overwritten by later calls to the triplestore.
 
-  If `lang` is not NULL, it must be a valid XML language abbreviation,
-  like "en". Only used if `literal` is non-zero.
+  Parameters:
+      s, p, o: Criteria to match. Two of these must be non-NULL.
+      d: If not NULL, the required datatype of literal objects.
+      fallback: Value to return if no matches are found.
+      any: If non-zero, return first matching value.
 
-  If redland is not available, it is equivalent to triplestore_find().
+  Returns a pointer to the value of the `s`, `p` or `o` that is NULL.
+  On error NULL is returned.
  */
-const Triple *triplestore_find2(TripleState *state,
-                                const char *s, const char *p, const char *o,
-                                int literal, const char *lang);
+const char *triplestore_value(TripleStore *ts, const char *s, const char *p,
+                              const char *o, const char *d,
+                              const char *fallback, int any);
 
 
 #endif /* _TRIPLESTORE_H */
diff --git a/src/utils/plugin.c b/src/utils/plugin.c
index 901d96235..817861b79 100644
--- a/src/utils/plugin.c
+++ b/src/utils/plugin.c
@@ -249,7 +249,8 @@ const PluginAPI *plugin_load(PluginInfo *info, const char *name,
            info->symbol, filepath, dsl_error());
 
     if (!registered_api && handle) {
-      if (handle) (void)dsl_close(handle);
+      if (handle && dsl_close(handle))
+        err(1, "error closing \"%s\": %s", filepath, dsl_error());
       handle = NULL;
     }
   }
diff --git a/storages/rdf/dlite-rdf.c b/storages/rdf/dlite-rdf.c
index 85aa793af..bbbd51db7 100644
--- a/storages/rdf/dlite-rdf.c
+++ b/storages/rdf/dlite-rdf.c
@@ -292,7 +292,7 @@ static const char *getobj(RdfStorage *rdf, const char *s, const char *p,
 {
   TripleStore *ts = rdf->ts;
   const Triple *t;
-  if (!(t = triplestore_find_first(ts, s, p, NULL))) {
+  if (!(t = triplestore_find_first(ts, s, p, NULL, NULL))) {
     if (verbose) err(1, "missing s='%s' p='%s': %s", s, p, rdf->location);
     return NULL;
   }
@@ -305,7 +305,7 @@ static int count(TripleStore *ts, const char *s, const char *p, const char *o)
   TripleState state;
   int n=0;
   triplestore_init_state(ts, &state);
-  while (triplestore_find(&state, s, p, o)) n++;
+  while (triplestore_find(&state, s, p, o, NULL)) n++;
   triplestore_deinit_state(&state);
   return n;
 }
@@ -326,32 +326,39 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
   int ok=0, n, j;
   char uuid[DLITE_UUID_LENGTH+1], muuid[DLITE_UUID_LENGTH+1];
   char *pid=NULL, *mid=NULL, *propiri=NULL;
+  const char *value;
 
   errno = 0;
-  dlite_get_uuid(uuid, id);
-  pid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, uuid) : NULL;
 
   /* find instance and metadata UUIDs */
-  triplestore_init_state(ts, &state);
-  while ((t2 = triplestore_find(&state, pid, _P ":hasMeta", NULL))) {
-    if (t) FAIL1("ID must be provided if storage holds "
-                 "more than one instance: %s", s->location);
-    t = t2;
-  }
-  triplestore_deinit_state(&state);
-  if (t) {
-    dlite_get_uuid(muuid, t->o);
+  if (id) {
+    dlite_get_uuid(uuid, id);
+    pid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, uuid) : NULL;
+    if (!(value = triplestore_value(ts, pid, _P ":hasMeta", NULL, NULL,
+                                  NULL, 0)))
+      FAILCODE2(dliteSearchError,
+                "cannot find instance '%s' in RDF storage: %s",
+                pid, s->location);
+    dlite_get_uuid(muuid, value);
   } else {
     triplestore_init_state(ts, &state);
-    while ((t2 = triplestore_find(&state, pid, _P ":hasURI", NULL))) {
-      if (t) FAIL1("ID must be provided if storage holds "
-                   "more than one instance: %s", s->location);
-      t = t2;
+    if ((t = triplestore_find(&state, NULL, _P ":hasMeta", NULL, NULL))) {
+      pid = strdup(t->s);
+      dlite_get_uuid(muuid, t->o);
     }
+    t2 = triplestore_find(&state, NULL, _P ":hasMeta", NULL, NULL);
     triplestore_deinit_state(&state);
-    dlite_get_uuid(muuid, DLITE_ENTITY_SCHEMA);
+    if (!t) FAILCODE1(dliteSearchError,
+                      "no instances in RDF storage: %s", s->location);
+
+    if (t2) FAILCODE1(dliteSearchError, "ID must be provided if storage "
+                      "holds more than one instance: %s", s->location);
+    if (!(value = triplestore_value(ts, pid, _P ":hasUUID", NULL, NULL,
+                                    NULL, 0)))
+      FAILCODE2(dliteInconsistentDataError, "instance '%s' has no "
+                _P ":hasUUID relation in RDF storage: %s", pid, s->location);
+    dlite_get_uuid(uuid, value);
   }
-  if (!t) FAIL2("no instance with id '%s' in store: %s", id, s->location);
 
   /* get/load metadata */
   mid = (s->base_uri) ? aprintf("%s:%s", s->base_uri, muuid) : NULL;
@@ -364,12 +371,12 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     const char *name, *val;
     if (!(dims = calloc(meta->_ndimensions, sizeof(size_t))))
       FAILCODE(dliteMemoryError, "allocation failure");
-    if (triplestore_find_first(ts, pid, _P ":hasDimensionValue", NULL)) {
+    if (triplestore_find_first(ts, pid, _P ":hasDimensionValue", NULL, NULL)) {
       /* -- read dimension values */
       n = 0;
       triplestore_init_state(ts, &state);
       while ((t = triplestore_find(&state, pid, _P ":hasDimensionValue",
-                                   NULL))) {
+                                   NULL, NULL))) {
         char *dimval = strdup(t->o);
         if (!(name = getobj(s, dimval, _P ":hasLabel", 1))) goto fail;
         j = dlite_meta_get_dimension_index(meta, name);
@@ -394,14 +401,17 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
   }
 
   if (!(inst = dlite_instance_create(meta, dims, (id) ? id : uuid))) goto fail;
-  if (!inst->uri && (t = triplestore_find_first(ts, pid, _P ":hasURI", NULL)))
+  if (!inst->uri && (t = triplestore_find_first(ts, pid, _P ":hasURI", NULL,
+                                                NULL)))
     inst->uri = strdup(t->o);
 
   /* FIXME - should have been called by dlite_instance_create() */
   if (dlite_instance_is_meta(inst)) dlite_meta_init((DLiteMeta *)inst);
 
   n = 0;
-  while ((t = triplestore_find(&state, pid, _P ":hasPropertyValue", NULL))) {
+  triplestore_init_state(ts, &state);
+  while ((t = triplestore_find(&state, pid, _P ":hasPropertyValue", NULL,
+                               NULL))) {
     /* -- read property values */
     DLiteProperty *p;
     const char *name, *val;
@@ -447,7 +457,8 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     /* -- read dimensions */
     d = dlite_instance_get_property(inst, "dimensions");
     triplestore_init_state(ts, &state);
-    while ((t = triplestore_find(&state, pid, _P ":hasDimension", NULL))) {
+    while ((t = triplestore_find(&state, pid, _P ":hasDimension", NULL,
+                                 NULL))) {
       propiri = strdup(t->o);
       if (!(str = getobj(s, propiri, _P ":hasLabel", 1))) goto fail;
       d->name = strdup(str);
@@ -463,7 +474,8 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
     /* -- read properties */
     p = dlite_instance_get_property(inst, "properties");
     triplestore_init_state(ts, &state);
-    while ((t = triplestore_find(&state, pid, _P ":hasProperty", NULL))) {
+    while ((t = triplestore_find(&state, pid, _P ":hasProperty", NULL,
+                                 NULL))) {
       const char *name, *typename, *shape, *unit, *descr;
 
       /* save propiri so it is not overwritten by getobj() */
@@ -520,7 +532,6 @@ DLiteInstance *rdf_load_instance(const DLiteStorage *storage, const char *id)
   if (propiri) free(propiri);
   if (dims) free(dims);
   if (!ok && inst) dlite_instance_decref(inst);
-  triplestore_deinit_state(&state);
   return (ok) ? inst : NULL;
 }
 
@@ -559,10 +570,10 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
     triplestore_add_uri(ts, inst->uuid, "rdf:type", _P ":Entity");
   else
     triplestore_add_uri(ts, inst->uuid, "rdf:type", _P ":Object");
-  triplestore_add(ts, inst->uuid, _P ":hasUUID", inst->uuid);
-  triplestore_add(ts, inst->uuid, _P ":hasMeta", inst->meta->uri);
+  triplestore_add(ts, inst->uuid, _P ":hasUUID", inst->uuid, "xsd:anyURI");
+  triplestore_add(ts, inst->uuid, _P ":hasMeta", inst->meta->uri, NULL);
   if (inst->uri)
-    triplestore_add(ts, inst->uuid, _P ":hasURI", inst->uri);
+    triplestore_add(ts, inst->uuid, _P ":hasURI", inst->uri, NULL);
 
   /* Describe metadata with spesialised properties */
   if (meta && s->fmtflags & fmtMetaAnnot) {
@@ -576,7 +587,7 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       if (!(b1 = get_blank_node(ts, buf))) goto fail;
       triplestore_add_uri(ts, inst->uuid, _P ":hasDimension", b1);
       triplestore_add_uri(ts, b1, "rdf:type", _P ":Dimension");
-      triplestore_add(ts, b1, _P ":hasLabel", d->name);
+      triplestore_add(ts, b1, _P ":hasLabel", d->name, "xsd:Name");
       triplestore_add_en(ts, b1, _P ":hasDescription", d->description);
       free(b1);
     }
@@ -591,18 +602,19 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       if (!(b2 = get_blank_node(ts, buf2))) goto fail;
       triplestore_add_uri(ts, inst->uuid, _P ":hasProperty", b1);
       triplestore_add_uri(ts, b1, "rdf:type", _P ":Property");
-      triplestore_add(ts, b1, _P ":hasLabel", p->name);
-      triplestore_add(ts, b1, _P ":hasType", typename);
+      triplestore_add(ts, b1, _P ":hasLabel", p->name, "xsd:Name");
+      triplestore_add(ts, b1, _P ":hasType", typename, "xsd:Name");
       if (p->ndims)
         triplestore_add_uri(ts, b1, _P ":hasFirstShape", b2);
       if (p->unit)
-        triplestore_add(ts, b1, _P ":hasUnit", p->unit);
+        triplestore_add(ts, b1, _P ":hasUnit", p->unit, "xsd:Name");
       if (p->description)
         triplestore_add_en(ts, b1, _P ":hasDescription", p->description);
 
       if (p->dims) {
         triplestore_add_uri(ts, b2, "rdf:type", _P ":Shape");
-        triplestore_add(ts, b2, _P ":hasDimensionExpression", p->dims[0]);
+        triplestore_add(ts, b2, _P ":hasDimensionExpression", p->dims[0],
+                        "xsd:string");
       }
       for (j=1; j < p->ndims; j++) {
         char *b;
@@ -610,7 +622,8 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
         if (!(b = get_blank_node(ts, buf2))) goto fail;
         triplestore_add_uri(ts, b2, _P ":hasNextShape", b);
         triplestore_add_uri(ts, b, "rdf:type", _P ":Shape");
-        triplestore_add(ts, b, _P ":hasDimensionExpression", p->dims[j]);
+        triplestore_add(ts, b, _P ":hasDimensionExpression", p->dims[j],
+                        "xsd:string");
         free(b2);
         b2 = b;
       }
@@ -628,9 +641,8 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       asnprintf(&buf, &bufsize, "%d",
                 (int)dlite_instance_get_dimension_size_by_index(inst, i));
       triplestore_add_uri(ts, inst->uuid, _P ":hasDimensionValue", b1);
-      triplestore_add(ts, b1, _P ":hasLabel", name);
-      triplestore_add2(ts, b1, _P ":hasDimensionSize", buf,
-                       1, NULL, "xsd:integer");
+      triplestore_add(ts, b1, _P ":hasLabel", name, "xsd:Name");
+      triplestore_add(ts, b1, _P ":hasDimensionSize", buf, "xsd:integer");
       free(b1);
     }
 
@@ -645,11 +657,10 @@ int rdf_save_instance(DLiteStorage *storage, const DLiteInstance *inst)
       triplestore_add_uri(ts, inst->uuid, _P ":hasPropertyValue", b1);
       triplestore_add_uri(ts, b1, "rdf:type", "owl:NamedIndividual");
       triplestore_add_uri(ts, b1, "rdf:type", _P ":PropertyValue");
-      triplestore_add(ts, b1, _P ":hasLabel", name);
+      triplestore_add(ts, b1, _P ":hasLabel", name, "xsd:Name");
       dlite_property_aprint(&buf, &bufsize, 0, ptr, p, dims, 0, -2,
                             dliteFlagRaw | dliteFlagStrip);
-      triplestore_add2(ts, b1, _P ":hasValue", buf, 1, NULL,
-                       "rdf:PlainLiteral");
+      triplestore_add(ts, b1, _P ":hasValue", buf, "rdf:PlainLiteral");
       free(b1);
     }
   }
@@ -703,7 +714,7 @@ int rdf_iter_next(void *iter, char *buf)
   RdfIter *riter = iter;
   const Triple *t;
   while (1) {
-    t = triplestore_find(&riter->state, NULL, _P ":hasMeta", NULL);
+    t = triplestore_find(&riter->state, NULL, _P ":hasMeta", NULL, NULL);
     if (!t) return 1;
     if (!riter->pattern) break;
     if (globmatch(riter->pattern, t->o) == 0) break;


Test patch:
diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml
index b170cbc92..55ed8ce59 100644
--- a/.github/workflows/ci_tests.yml
+++ b/.github/workflows/ci_tests.yml
@@ -10,6 +10,10 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v4
 
+      # We do not install rdflib0-dev here, since we want to test
+      # DLite both with and without rdflib (Redland) support and the
+      # case with rdflib is already covered by the ci_build_wheel
+      # workflow for Linux systems.
     - name: Install dependencies
       run: |
         sudo apt-get update --fix-missing && \
diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt
index b6280f2ee..a9746189f 100644
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@ -23,6 +23,7 @@ set(tests
   test_postgresql2_read
   test_ref_type
   test_pydantic
+  test_relation
   test_iri
 )
 
diff --git a/bindings/python/tests/test_collection.py b/bindings/python/tests/test_collection.py
index 72a5b7247..3d9dee18b 100755
--- a/bindings/python/tests/test_collection.py
+++ b/bindings/python/tests/test_collection.py
@@ -3,6 +3,8 @@
 from pathlib import Path
 
 import dlite
+from dlite.testutils import raises
+
 
 thisdir = Path(__file__).resolve().parent
 outdir = thisdir / "output"
@@ -162,6 +164,30 @@
 assert isinstance(coll["meta"], dlite.Metadata)
 
 
+# Test Collection.value()
+assert coll.value(s="dog", p="is-a") == "animal"
+assert coll.value(p="_is-a", o="Instance", any=1) == "inst1"
+assert coll.value(p="is-a", o="x", default="y", any=1) == "y"
+assert coll.value(s="meta", p="_has-uuid", d="xsd:anyURI") == (
+    "96f31fc3-3838-5cb8-8d90-eddee6ff59ca"
+)
+
+with raises(dlite.DLiteTypeError):
+    coll.value(s="dog", p="is-a", o="animal")
+
+with raises(dlite.DLiteTypeError):
+    coll.value()
+
+with raises(dlite.DLiteSearchError):
+    coll.value(s="dog", p="x")
+
+with raises(dlite.DLiteSearchError):
+    coll.value(p="_is-a", o="Instance")
+
+with raises(dlite.DLiteSearchError):
+    coll.value(s="meta", p="_has-uuid", d="xsd:int")
+
+
 # String representation
 s = str(coll)
 print(s)
diff --git a/bindings/python/tests/test_relation.py b/bindings/python/tests/test_relation.py
new file mode 100644
index 000000000..34db39817
--- /dev/null
+++ b/bindings/python/tests/test_relation.py
@@ -0,0 +1,18 @@
+import dlite
+from dlite.testutils import raises
+
+
+rel1 = dlite.Relation("s1", "p1", "o1")
+assert (rel1.s, rel1.p, rel1.o) == ("s1", "p1", "o1")
+assert rel1.d == None
+
+rel2 = dlite.Relation("s2", "p2", "o2", "d2")
+assert (rel2.s, rel2.p, rel2.o) == ("s2", "p2", "o2")
+assert rel2.d == "d2"
+
+rel = dlite.Relation("s", "p", "3")
+with raises(TypeError):
+    rel = dlite.Relation("s", "p", 3)
+
+with raises(TypeError):
+    rel = dlite.Relation("s", "p", "o", "d", "id", "non-expected-argument")
diff --git a/bindings/python/tests/test_storage.py b/bindings/python/tests/test_storage.py
index 4b93c394b..6a6f2ef58 100755
--- a/bindings/python/tests/test_storage.py
+++ b/bindings/python/tests/test_storage.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 from pathlib import Path
 
+import numpy as np
+
 import dlite
 from dlite.testutils import raises
 
@@ -36,6 +38,17 @@
 # Create an instance
 inst = myentity(dimensions=[2, 3], id="my-data")
 inst["a-bool-array"] = True, False
+inst["a-relation"] = "subj", "pred", "obj"
+inst["a-relation-array"] = [
+    ("s1", "p1", "o1"),
+    ("s2", "p2", "o2"),
+]
+assert np.all(inst["a-bool-array"] == [True, False])
+assert inst["a-relation"] == dlite.Relation("subj", "pred", "obj")
+assert np.all(inst["a-relation-array"] == [
+    dlite.Relation("s1", "p1", "o1"),
+    dlite.Relation("s2", "p2", "o2"),
+])
 
 # Test Storage.save()
 with dlite.Storage("json", outdir / "test_storage_tmp.json", "mode=w") as s:
@@ -51,7 +64,6 @@
 del inst
 inst = dlite.Instance.from_url(f"json://{outdir}/test_storage_inst.json#my-data")
 
-
 # Test yaml
 if HAVE_YAML:
     print('--- testing yaml')
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 47bb42cbd..6bbe9b8d0 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -13,6 +13,7 @@ set(tests
   test_dlitebson
   test_metamodel
   test_store
+  test_triple
   test_triplestore
   test_collection
   test_schemas
diff --git a/src/tests/test_collection.c b/src/tests/test_collection.c
index fa34fbd1f..5bcc2dd11 100644
--- a/src/tests/test_collection.c
+++ b/src/tests/test_collection.c
@@ -6,6 +6,7 @@
 #include <string.h>
 
 #include "minunit/minunit.h"
+#include "utils/err.h"
 #include "dlite.h"
 #include "dlite-collection.h"
 #include "dlite-macros.h"
@@ -26,15 +27,19 @@ MU_TEST(test_collection_add_relation)
   mu_assert_int_eq(0, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 
-  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
+  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
   mu_assert_int_eq(3, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
   mu_assert_int_eq(3, coll->nrelations);
 
   /* dublicate... */
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
   mu_assert_int_eq(3, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 }
@@ -42,8 +47,8 @@ MU_TEST(test_collection_add_relation)
 
 MU_TEST(test_collection_remove_relations)
 {
-  mu_assert_int_eq(2, dlite_collection_remove_relations(coll, NULL,
-							"is_a", "animal"));
+  mu_assert_int_eq(2, dlite_collection_remove_relations(coll, NULL, "is_a",
+                                                        "animal", NULL));
   mu_assert_int_eq(1, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 }
@@ -53,26 +58,94 @@ MU_TEST(test_collection_find)
 {
   const DLiteRelation *r;
   DLiteCollectionState state;
-  int nanimals=0;
-
-  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal"));
-  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog"));
-  mu_check(!dlite_collection_add_relation(coll, "car", "is_a", "vehicle"));
-  mu_assert_int_eq(4, dlite_instance_get_dimension_size((DLiteInstance *)coll,
+  int nanimals=0, n=0;
+
+  mu_check(!dlite_collection_add_relation(coll, "dog", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "is_a", "animal",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "terrier", "is_a", "dog",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "car", "is_a", "vehicle",
+                                          NULL));
+  mu_check(!dlite_collection_add_relation(coll, "cat", "has_color", "black",
+                                          "xsd:string"));
+  mu_check(!dlite_collection_add_relation(coll, "car", "has_color", "black",
+                                          "xsd:string"));
+  mu_assert_int_eq(6, dlite_instance_get_dimension_size((DLiteInstance *)coll,
 							"nrelations"));
 
   dlite_collection_init_state(coll, &state);
+  printf("\nRelations:\n");
+  while ((r = dlite_collection_find(coll, &state, NULL, NULL, NULL, NULL)))
+    printf("  %-10s %-10s %-10s %s\n", r->s, r->p, r->o, r->d);
+
+  dlite_collection_reset_state(&state);
   printf("\nAnimals:\n");
-  while ((r = dlite_collection_find(coll, &state, NULL, "is_a", "animal"))) {
+  while ((r = dlite_collection_find(coll, &state, NULL, "is_a", "animal",
+                                    NULL))) {
     printf("  %s\n", r->s);
     nanimals++;
   }
   mu_assert_int_eq(2, nanimals);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, NULL, "xsd:int")) n++;
+  mu_assert_int_eq(0, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, NULL, "xsd:string"))
+    n++;
+  mu_assert_int_eq(2, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, "black", "xsd:string"))
+    n++;
+  mu_assert_int_eq(2, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, NULL, NULL, "red", "xsd:string"))
+    n++;
+  mu_assert_int_eq(0, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, "car", NULL, NULL, "xsd:string"))
+    n++;
+  mu_assert_int_eq(1, n);
+
+  dlite_collection_reset_state(&state);
+  n = 0;
+  while (dlite_collection_find(coll, &state, "car", NULL, NULL, ""))
+    n++;
+  mu_assert_int_eq(1, n);
+
   dlite_collection_deinit_state(&state);
 }
 
 
+MU_TEST(test_collection_value)
+{
+  mu_assert_string_eq("terrier", dlite_collection_value(coll, NULL, "is_a",
+                                                        "dog", NULL, NULL, 0));
+  mu_assert_string_eq("dog", dlite_collection_value(coll, NULL, "is_a",
+                                                    "animal", NULL, NULL, 1));
+
+ ErrTry:
+  mu_assert_string_eq(NULL, dlite_collection_value(coll, NULL, "is_a",
+                                                   "animal", NULL, NULL, 0));
+  mu_assert_string_eq(NULL, dlite_collection_value(coll, NULL, "is_a",
+                                                   "mammal", NULL, NULL, 0));
+ ErrCatch(dliteSearchError):
+  break;
+ ErrEnd;
+}
+
+
 MU_TEST(test_collection_add)
 {
   char *path, *uri;
@@ -165,29 +238,30 @@ MU_TEST(test_collection_load)
 {
   DLiteCollection *coll2;
   char *collpath = STRINGIFY(dlite_SOURCE_DIR) "/src/tests/coll.json";
-  DLiteStoragePathIter *iter;
-  const char *path;
+  //DLiteStoragePathIter *iter;
+  //const char *path;
 
   dlite_storage_paths_append(STRINGIFY(dlite_SOURCE_DIR) "/src/tests/*.json");
 
-  printf("\n\nStorage paths:\n");
-  iter = dlite_storage_paths_iter_start();
-  while ((path = dlite_storage_paths_iter_next(iter)))
-    printf("  - %s\n", path);
-  printf("\n");
-  dlite_storage_paths_iter_stop(iter);
+  //printf("\n\nStorage paths:\n");
+  //iter = dlite_storage_paths_iter_start();
+  //while ((path = dlite_storage_paths_iter_next(iter)))
+  //  printf("  - %s\n", path);
+  //printf("\n");
+  //dlite_storage_paths_iter_stop(iter);
 
   FILE *fp = fopen(collpath, "r");
   coll2 = (DLiteCollection *)
     dlite_json_fscan(fp, NULL, "http://onto-ns.com/meta/0.1/Collection");
   fclose(fp);
-  printf("\n\n--- coll2: %p ---\n", (void *)coll2);
-  dlite_json_print((DLiteInstance *)coll2);
-  printf("----------------------\n");
+  //printf("\n\n--- coll2: %p ---\n", (void *)coll2);
+  //dlite_json_print((DLiteInstance *)coll2);
+  //printf("----------------------\n");
+
   const DLiteInstance *inst = dlite_collection_get(coll2, "inst");
-  printf("\n--- inst: %p ---\n", (void *)inst);
-  dlite_json_print((DLiteInstance *)inst);
-  printf("----------------------\n");
+  //printf("\n--- inst: %p ---\n", (void *)inst);
+  //dlite_json_print((DLiteInstance *)inst);
+  //printf("----------------------\n");
 
   dlite_instance_decref((DLiteInstance *)inst);
   dlite_collection_decref(coll2);
@@ -197,6 +271,8 @@ MU_TEST(test_collection_load)
 MU_TEST(test_collection_free)
 {
   dlite_collection_decref(coll);
+
+  dlite_finalize();  // for checking memory leaks
 }
 
 
@@ -212,8 +288,8 @@ MU_TEST_SUITE(test_suite)
 
   MU_RUN_TEST(test_collection_add_relation);
   MU_RUN_TEST(test_collection_remove_relations);
-
   MU_RUN_TEST(test_collection_find);
+  MU_RUN_TEST(test_collection_value);
 
   MU_RUN_TEST(test_collection_add);
   MU_RUN_TEST(test_collection_get);
diff --git a/src/tests/test_entity.c b/src/tests/test_entity.c
index 3b502aee0..53ee608bf 100644
--- a/src/tests/test_entity.c
+++ b/src/tests/test_entity.c
@@ -379,7 +379,8 @@ MU_TEST(test_instance_get_hash)
   mu_assert_string_eq(hash, gethash(s, (DLiteInstance *)inst->meta->meta));
 
   // basic metadata schema
-  hash = "fc7a634b6f98306b04bfd44f94bddb1d2a29970e8c0314e4d6cc977e8d6920da";
+  //hash = "fc7a634b6f98306b04bfd44f94bddb1d2a29970e8c0314e4d6cc977e8d6920da";
+  hash = "0e48cc58a1298ef87c9826e4978923c26e5369c1f87fc6216c67de358d41596b";
   mu_assert_string_eq(hash, gethash(s, (DLiteInstance *)inst->meta->meta->meta));
 
   // basic metadata schema
diff --git a/src/tests/test_json.c b/src/tests/test_json.c
index a66e0eb1a..0206fb2ea 100644
--- a/src/tests/test_json.c
+++ b/src/tests/test_json.c
@@ -102,9 +102,10 @@ MU_TEST(test_sprint)
 
   /* Tests for proper quoting */
   DLiteCollection *coll = dlite_collection_create(NULL);
-  dlite_collection_add_relation(coll, "s", "p", "\"o\"");
+  dlite_collection_add_relation(coll, "s", "p", "\"o\"", NULL);
   m = dlite_json_sprint(buf, sizeof(buf), (DLiteInstance *)coll, 2, 0);
-  const DLiteRelation *rel = dlite_collection_find_first(coll, "s", "p", NULL);
+  const DLiteRelation *rel = dlite_collection_find_first(coll, "s", "p", NULL,
+                                                         NULL);
   mu_assert_string_eq("\"o\"", rel->o);
   dlite_instance_decref((DLiteInstance *)coll);
   //printf("\n--------------------------------------------------------\n");
diff --git a/src/tests/test_property.c b/src/tests/test_property.c
index db77b412b..aee0d3bfa 100644
--- a/src/tests/test_property.c
+++ b/src/tests/test_property.c
@@ -90,12 +90,13 @@ MU_TEST(test_print)
                       "\"dims\": [\"M\", \"N\"], \"unit\": \"m\", "
                       "\"description\": \"about x...\"}", s);
 
-  DLiteRelation rel = {"subject", "predicate", "object", "id"};
+  DLiteRelation rel = {"subject", "predicate", "object", "datatype", NULL};
   prop.type = dliteRelation;
   prop.size = sizeof(rel);
   m = dlite_property_aprint(&s, &size, n, &rel, &prop, NULL, 0, -2, 0);
-  mu_assert_int_eq(34, m);
-  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\"]", s);
+  mu_assert_int_eq(46, m);
+  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\", \"datatype\"]",
+                      s);
 
   free(s);
 }
diff --git a/src/tests/test_triple.c b/src/tests/test_triple.c
new file mode 100644
index 000000000..64ecafb29
--- /dev/null
+++ b/src/tests/test_triple.c
@@ -0,0 +1,77 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "utils/session.h"
+#include "minunit/minunit.h"
+#include "dlite-misc.h"
+#include "triple.h"
+
+
+
+MU_TEST(test_set)
+{
+  Triple t;
+  triple_set(&t, "book", "is-a", "thing", NULL, NULL);
+  mu_assert_string_eq("book", t.s);
+  mu_assert_string_eq("is-a", t.p);
+  mu_assert_string_eq("thing", t.o);
+  mu_assert_string_eq(NULL, t.d);
+  mu_assert_string_eq("e86ddacd5fd2f3f8f46543fc8096eab96a12c440", t.id);
+
+  triple_reset(&t, "subject", "predicate", "object", "datatype", NULL);
+  mu_assert_string_eq("subject", t.s);
+  mu_assert_string_eq("predicate", t.p);
+  mu_assert_string_eq("object", t.o);
+  mu_assert_string_eq("datatype", t.d);
+  triple_clean(&t);
+}
+
+
+MU_TEST(test_get_id)
+{
+  char *id;
+  Triple t = {"s", "p", "o", "@en", NULL};
+  id = triple_get_id(NULL, t.s, t.p, t.o, t.d);
+  mu_assert_string_eq("fac793e0cf9731b05a1554d16f834f03bbfe8306", id);
+  free(id);
+}
+
+
+MU_TEST(test_copy)
+{
+  Triple t = {"s", "p", "o", "@en", NULL}, t2, *tp;
+  tp = triple_copy(&t2, &t);
+  mu_assert_ptr_eq(tp, &t2);
+  mu_assert_string_eq("s", t2.s);
+  mu_assert_string_eq("p", t2.p);
+  mu_assert_string_eq("o", t2.o);
+  mu_assert_string_eq("@en", t2.d);
+  mu_assert_string_eq(NULL, t2.id);
+  triple_clean(&t2);
+}
+
+
+MU_TEST(test_finalize)
+{
+  dlite_finalize();  // for memory checking
+}
+
+
+/***********************************************************************/
+
+MU_TEST_SUITE(test_suite)
+{
+  MU_RUN_TEST(test_set);
+  MU_RUN_TEST(test_get_id);
+  MU_RUN_TEST(test_copy);
+  MU_RUN_TEST(test_finalize);
+}
+
+
+int main()
+{
+  MU_RUN_SUITE(test_suite);
+  MU_REPORT();
+  return (minunit_fail) ? 1 : 0;
+}
diff --git a/src/tests/test_triplestore.c b/src/tests/test_triplestore.c
index 1f8809997..4877daa2a 100644
--- a/src/tests/test_triplestore.c
+++ b/src/tests/test_triplestore.c
@@ -2,8 +2,10 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include "utils/err.h"
 #include "utils/session.h"
 #include "minunit/minunit.h"
+#include "dlite-misc.h"
 #include "triplestore.h"
 
 
@@ -16,36 +18,33 @@ MU_TEST(test_create)
   errno = 0;
 }
 
-MU_TEST(test_triple)
-{
-  Triple t;
-  char *id;
-  triple_set(&t, "book", "is-a", "thing", NULL);
-  id = triple_get_id(NULL, t.s, t.p, t.o);
-  mu_assert_string_eq("e86ddacd5fd2f3f8f46543fc8096eab96a12c440", id);
-  triple_clean(&t);
-  free(id);
-}
 
 MU_TEST(test_add)
 {
   Triple t[] = {
-    {"book", "is-a", "thing", NULL},
-    {"table", "is-a", "thing", NULL},
-    {"book", "is-ontop-of", "table", NULL},
-    {"write", "is-a", "action", NULL},
-    {"walk", "is-a", "action", NULL},
-    {"write", "is-a", "action", NULL}  /* dublicate */
+    {"book", "is-a", "thing", NULL, NULL},
+    {"table", "is-a", "thing", NULL, NULL},
+    {"table", "is-a", "furniture", NULL, NULL},
+    {"book", "is-ontop-of", "table", NULL, NULL},
+    {"write", "is-a", "action", NULL, NULL},
+    {"walk", "is-a", "action", NULL, NULL},
+    {"write", "is-a", "action", NULL, NULL}  /* dublicate */
   };
   size_t n = sizeof(t) / sizeof(t[0]);
 
-  mu_check(0 == triplestore_length(ts));
+  mu_assert_int_eq(0, triplestore_length(ts));
   mu_assert_int_eq(0, triplestore_add_triples(ts, t, n));
-  mu_check(5 == triplestore_length(ts));
-
-  mu_assert_int_eq(0, triplestore_add(ts, "read", "is-a", "action"));
-  mu_check(6 == triplestore_length(ts));
-
+  mu_assert_int_eq(6, triplestore_length(ts));
+
+  mu_assert_int_eq(0, triplestore_add_en(ts, "book", "has-title",
+                                         "The Infinite Book"));
+  mu_assert_int_eq(0, triplestore_add_uri(ts, "book", "has-weight",
+                                          "book-weight"));
+  mu_assert_int_eq(0, triplestore_add(ts, "book-weight", "has-value",
+                                      "0.6", "xsd:double"));
+  mu_assert_int_eq(0, triplestore_add(ts, "book-weight", "has-unit",
+                                      "kg", "xsd:string"));
+  mu_assert_int_eq(10, triplestore_length(ts));
 }
 
 
@@ -57,50 +56,145 @@ MU_TEST(test_next)
   triplestore_init_state(ts, &state);
   printf("\n");
   while ((t = triplestore_next(&state)))
-    printf("  %-11s %-11s %-11s %s\n", t->s, t->p, t->o, t->id);
+    printf("  %-11s %-11s %-20s %s\n", t->s, t->p, t->o, t->d);
   triplestore_deinit_state(&state);
 }
 
 
+MU_TEST(test_poll)
+{
+  TripleState state;
+  const Triple *t;
+
+  triplestore_init_state(ts, &state);
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("book", t->s);
+
+  t = triplestore_next(&state);
+  mu_assert_string_eq("book", t->s);
+
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("table", t->s);
+
+  triplestore_reset_state(&state);
+  t = triplestore_poll(&state);
+  mu_assert_string_eq("book", t->s);
+
+  triplestore_deinit_state(&state);
+}
+
 
 MU_TEST(test_find)
 {
   int n;
-  const Triple *t;
   TripleState state;
+  const Triple *t;
 
-  t = triplestore_find_first(ts, NULL, "is-a", "table");
+  t = triplestore_find_first(ts, NULL, "is-a", "table", NULL);
   mu_check(t == NULL);
 
-  t = triplestore_find_first(ts, NULL, "is-ontop-of", "table");
+  t = triplestore_find_first(ts, NULL, "is-ontop-of", "table", NULL);
   mu_check(t);
   mu_assert_string_eq("book", t->s);
   mu_assert_string_eq("is-ontop-of", t->p);
   mu_assert_string_eq("table", t->o);
+  mu_assert_string_eq(NULL, t->d);
 
   triplestore_init_state(ts, &state);
   n = 0;
-  while (triplestore_find(&state, NULL, "is-a", "thing")) n++;
-  mu_assert_int_eq(2, n);
+  while (triplestore_find(&state, NULL, NULL, NULL, NULL)) n++;
+  mu_assert_int_eq(10, n);
   triplestore_deinit_state(&state);
 
   triplestore_init_state(ts, &state);
   n = 0;
-  while (triplestore_find(&state, NULL, "is-a", NULL)) n++;
+  while (triplestore_find(&state, NULL, "is-a", NULL, NULL)) n++;
   mu_assert_int_eq(5, n);
   triplestore_deinit_state(&state);
+
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, "is-a", "thing", NULL)) n++;
+  mu_assert_int_eq(2, n);
+  triplestore_deinit_state(&state);
+
+  /* Count IRIs */
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, NULL, NULL, "")) n++;
+  mu_assert_int_eq(7, n);
+  triplestore_deinit_state(&state);
+
+  triplestore_init_state(ts, &state);
+  n = 0;
+  while (triplestore_find(&state, NULL, NULL, NULL, "xsd:double")) n++;
+  mu_assert_int_eq(1, n);
+  triplestore_deinit_state(&state);
+
+  t = triplestore_find_first(ts, NULL, NULL, NULL, "xsd:double");
+  mu_check(t);
+  mu_assert_string_eq("book-weight", t->s);
+  mu_assert_string_eq("has-value", t->p);
+  mu_assert_string_eq("0.6", t->o);
+  mu_assert_string_eq("xsd:double", t->d);
+}
+
+
+MU_TEST(test_value)
+{
+  mu_assert_string_eq("action", triplestore_value(ts, "write", "is-a", NULL,
+                                                  NULL, NULL, 0));
+  mu_assert_string_eq("thing", triplestore_value(ts, "table", "is-a", NULL,
+                                                  NULL, NULL, 1));
+  mu_assert_string_eq("kg", triplestore_value(ts, "book-weight", "has-unit",
+                                              NULL, NULL, NULL, 0));
+  mu_assert_string_eq("book-weight", triplestore_value(ts, NULL, "has-unit",
+                                              "kg", NULL, NULL, 0));
+  mu_assert_string_eq("some-weight", triplestore_value(ts, NULL, "has-unit",
+                                              "µg", NULL, "some-weight", 0));
+
+
+  /* Check some failures */
+ ErrTry:
+  // more than one match
+  mu_assert_string_eq(NULL, triplestore_value(ts, "table", "is-a", NULL,
+                                              NULL, NULL, 0));
+  // no match (datatype does not match)
+  mu_assert_string_eq(NULL, triplestore_value(ts, "book-weight", "has-unit",
+                                              NULL, "xsd:float", NULL, 0));
+ ErrCatch(dliteSearchError):  // suppress errors
+  break;
+ ErrEnd;
+
+ ErrTry:
+  // at least 2 of s,p,o must be given
+  mu_assert_string_eq(NULL, triplestore_value(ts, NULL, "is-a",
+                                              NULL, NULL, NULL, 0));
+  mu_assert_string_eq(NULL, triplestore_value(ts, "book", "is-a",
+                                              "thing", NULL, NULL, 0));
+ ErrCatch(dliteTypeError):  // suppress errors
+  break;
+ ErrEnd;
 }
 
 
 MU_TEST(test_remove)
 {
-  mu_check(6 == triplestore_length(ts));
+  mu_assert_int_eq(10, triplestore_length(ts));
+
+  mu_check(!triplestore_remove(ts, NULL, "is-something", NULL, NULL));
+  mu_assert_int_eq(10, triplestore_length(ts));
 
-  mu_check(!triplestore_remove(ts, NULL, "is-something", NULL));
-  mu_check(6 == triplestore_length(ts));
+  mu_assert_int_eq(4, triplestore_remove(ts, "book", NULL, NULL, NULL));
+  mu_assert_int_eq(6, triplestore_length(ts));
+}
 
-  mu_assert_int_eq(2, triplestore_remove(ts, "book", NULL, NULL));
-  mu_check(4 == triplestore_length(ts));
+
+MU_TEST(test_clear)
+{
+  mu_assert_int_eq(6, triplestore_length(ts));
+  triplestore_clear(ts);
+  mu_assert_int_eq(0, triplestore_length(ts));
 }
 
 
@@ -108,8 +202,7 @@ MU_TEST(test_free)
 {
   triplestore_free(ts);
 
-  Session *s = session_get_default();
-  session_free(s);
+  dlite_finalize();  // for testing memory leaks
 }
 
 
@@ -119,11 +212,13 @@ MU_TEST(test_free)
 MU_TEST_SUITE(test_suite)
 {
   MU_RUN_TEST(test_create);
-  MU_RUN_TEST(test_triple);
   MU_RUN_TEST(test_add);
   MU_RUN_TEST(test_next);
+  MU_RUN_TEST(test_poll);
   MU_RUN_TEST(test_find);
+  MU_RUN_TEST(test_value);
   MU_RUN_TEST(test_remove);
+  MU_RUN_TEST(test_clear);
   MU_RUN_TEST(test_free);
 }
 
diff --git a/src/tests/test_type.c b/src/tests/test_type.c
index 1fb5314f0..aa836b1e6 100644
--- a/src/tests/test_type.c
+++ b/src/tests/test_type.c
@@ -209,7 +209,8 @@ MU_TEST(test_print)
   char *p=NULL, s[]="my source string", *q=s;
   DLiteInstance *inst = (DLiteInstance *)dlite_collection_create("myid");
   DLiteDimension d = {"name", "descr"};
-  DLiteRelation r = {"subject", "predicate", "object", "id"};
+  DLiteRelation r1 = {"subject", "predicate", "object", NULL,       NULL};
+  DLiteRelation r2 = {"subject", "predicate", "object", "datatype", NULL};
 
   mu_assert_int_eq(7, dlite_type_print(buf, sizeof(buf), &v, dliteFloat,
                                        sizeof(double), 0, -2, 0));
@@ -245,12 +246,17 @@ MU_TEST(test_print)
   mu_assert_string_eq("{\"name\": \"name\", \"description\": \"descr\"}", buf);
 
 
-  mu_assert_int_eq(34, dlite_type_print(buf, sizeof(buf), &r, dliteRelation,
+  mu_assert_int_eq(34, dlite_type_print(buf, sizeof(buf), &r1, dliteRelation,
                                        sizeof(DLiteRelation *), -1, -1, 0));
   mu_assert_string_eq("[\"subject\", \"predicate\", \"object\"]", buf);
 
+  mu_assert_int_eq(46, dlite_type_print(buf, sizeof(buf), &r2, dliteRelation,
+                                       sizeof(DLiteRelation *), -1, -1, 0));
+  mu_assert_string_eq("[\"subject\", \"predicate\", \"object\", \"datatype\"]",
+                      buf);
+
   buf[0] = '\0';
-  mu_assert_int_eq(34, dlite_type_print(buf, 0, &r, dliteRelation,
+  mu_assert_int_eq(34, dlite_type_print(buf, 0, &r1, dliteRelation,
                                        sizeof(DLiteRelation *), -1, -1, 0));
   mu_assert_int_eq(0, buf[0]);
 
@@ -470,19 +476,97 @@ MU_TEST(test_scan)
 
   /* dliteRelation */
   memset(&rel, 0, sizeof(DLiteRelation));
-
   s = "[\"subject\", \"predicate\", \"object\"]";
   n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
   mu_assert_int_eq(34, n);
   mu_assert_string_eq("subject",   rel.s);
   mu_assert_string_eq("predicate", rel.p);
   mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"datatype\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(46, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"datatype\", \"id\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(52, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(38, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\", \"\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(42, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "[\"subject\", \"predicate\", \"object\", \"\", \"id\"]";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(44, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
+
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(49, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq(NULL,        rel.d);
+  mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
+
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\", "
+    "\"d\": \"datatype\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(66, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
   mu_assert_string_eq(NULL,        rel.id);
+  triple_clean(&rel);
 
-  if (rel.s) free(rel.s);
-  if (rel.p) free(rel.p);
-  if (rel.o) free(rel.o);
-  if (rel.id) free(rel.id);
+  s = "{\"s\": \"subject\", \"p\": \"predicate\", \"o\": \"object\", "
+    "\"d\": \"datatype\", \"id\": \"id\"}";
+  n = dlite_type_scan(s, -1, &rel, dliteRelation, sizeof(DLiteRelation), 0);
+  mu_assert_int_eq(78, n);
+  mu_assert_string_eq("subject",   rel.s);
+  mu_assert_string_eq("predicate", rel.p);
+  mu_assert_string_eq("object",    rel.o);
+  mu_assert_string_eq("datatype",  rel.d);
+  mu_assert_string_eq("id",        rel.id);
+  triple_clean(&rel);
 }
 
 
diff --git a/storages/rdf/tests/test_rdf.c b/storages/rdf/tests/test_rdf.c
index 40babac7a..847ddb07e 100644
--- a/storages/rdf/tests/test_rdf.c
+++ b/storages/rdf/tests/test_rdf.c
@@ -99,13 +99,11 @@ MU_TEST(test_iter)
 
 MU_TEST(test_freedata)
 {
-  int nref;
-
-  nref = inst->_refcount;
-  while (nref--) dlite_instance_decref(inst);
-
-  nref = meta->_refcount;
-  while (nref--) dlite_meta_decref(meta);
+  if (meta) {
+    dlite_meta_decref(meta);
+    dlite_meta_decref(meta);
+  }
+  if (inst) dlite_instance_decref(inst);
 }