Parts, stores, and CONSTRUCT

SELECT, ASK, and DESCRIBE queries have something in common: they require access only to triple parts found in the current store. (In fact, in the case of an ASK query, no parts are needed; the result is a boolean.)

That is to say, no result yielded by one of these queries can refer to a resource or literal that is not mentioned in the store on which you ran the query.

CONSTRUCT, on the other hand, can produce triples that are composed of parts not found in the triple store being queried. For example, given the dataset

<> <> "something" . 

and the query

  ?x <> ?literal .  
  ?x ?p ?literal .  
  FILTER (isLiteral(?literal))  

the store contains three parts (two URIs and a literal), but the output features the additional resource not present in the store: <>.

What would happen if a CONSTRUCT query returned an AllegroGraph triple — that is, a compact vector containing UPI representations of parts? Everything would work, right up until you tried to fetch the value of the UPI representing <>, which isn't in the store. You'd see an error like this:

triple-store(11): (upi->value (db.agraph::coerce-to-upi (resource "")))  
Error: The UPI #(54 20 61 123 204 203 226 83 60 128 225 0) has not been  
       added to the triple-store's string dictionary.  
  [condition type: upi-not-in-string-table-error] 

(Note that all query results will exhibit this problem if considered outside their originating store — AllegroGraph can only resolve the UPIs when the triple-store is available. CONSTRUCT is a different case.)

In order to produce triples that can later be examined, the query engine must intern these resources in the store being queried (the approach taken in AllegroGraph 2). This is not an ideal solution: — the store might be read-only, or a federation, and in any case the store's string table would grow over time as new queries were run against the store.

AllegroGraph has a mechanism for referring to RDF parts outside of a parent store: future-parts. These are Lisp objects that store an RDF value in its expanded form, and they're perfect for solving this problem. However, they don't fit into a triple, which is a vector of bytes, optimized for storing UPIs. For this reason, CONSTRUCT queries using the algebra query engine don't return triples; instead, they can return 3-element vectors containing future-parts and UPIs.

Similarly, blank nodes are meaningless outside of the context of a containing triple-store; furthermore, a blank node generated in one store could conceivably collide with a blank node already present in another. In programmatic CONSTRUCT output, blank nodes are represented by integers.

You can trivially take the output and insert it into a triple store by mapping back from integers to blank nodes. Here is an example using the Lisp API:

(defun add-construct-output-to-store (faux-triples db &key (g (default-graph-upi db)))  
  (let ((bnode-mappings (make-hash-table)))  
    (flet ((part (x)  
             (typecase x  
                 (or (gethash x bnode-mappings)  
                     (setf (gethash x bnode-mappings)  
                           (new-blank-node :db db))))  
               (t x))))  
      (loop for tr in faux-triples do  
            (add-triple (part (svref tr 0))  ; s  
                        (part (svref tr 1))  ; p  
                        (part (svref tr 2))  ; o  
                        :db db  
                        :g g)))))