Repository: cayleygraph/cayley
Pull request #920 (2020-02-08 23:31:51)

LinkedQL: Collect Step
Adds a collect step to resolve lists.

### Todo
 - [x] Outputted JSON-LD should use `@list`
         The best solution I can come up with so far is: saving tags as quads and then resolve JSON-LD through the From RDF API.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cayleygraph/cayley/920)
<!-- Reviewable:end -->



Patch:
diff --git a/query/linkedql/steps/collect.go b/query/linkedql/steps/collect.go
new file mode 100644
index 0000000..5ce5f62
--- /dev/null
+++ b/query/linkedql/steps/collect.go
@@ -0,0 +1,58 @@
+package steps
+
+import (
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/cayley/query/linkedql"
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/voc"
+)
+
+func init() {
+	linkedql.Register(&Collect{})
+}
+
+var _ linkedql.IteratorStep = (*Collect)(nil)
+var _ linkedql.PathStep = (*Collect)(nil)
+
+// Collect corresponds to .view().
+type Collect struct {
+	From linkedql.PathStep `json:"from"`
+	Name quad.IRI          `json:"name"`
+}
+
+// Description implements Step.
+func (s *Collect) Description() string {
+	return "Recursively resolves values of a list (also known as RDF collection)"
+}
+
+// BuildIterator implements linkedql.IteratorStep.
+func (s *Collect) BuildIterator(qs graph.QuadStore, ns *voc.Namespaces) (query.Iterator, error) {
+	return linkedql.NewValueIteratorFromPathStep(s, qs, ns)
+}
+
+var (
+	first  = quad.IRI("rdf:first").Full()
+	rest   = quad.IRI("rdf:rest").Full()
+	rdfNil = quad.IRI("rdf:nil").Full()
+)
+
+// BuildPath implements linkedql.PathStep.
+func (s *Collect) BuildPath(qs graph.QuadStore, ns *voc.Namespaces) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs, ns)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath.
+		Out(s.Name).
+		Save(first, string(first)).
+		Save(rest, string(rest)).
+		Or(
+			fromPath.Out(s.Name).FollowRecursive(rest, -1, nil).
+				Save(first, string(first)).
+				Save(rest, string(rest)),
+		).
+		Or(fromPath.Save(s.Name, string(s.Name)))
+	return p, nil
+}


Test patch:
diff --git a/query/linkedql/steps/test-cases/collect.json b/query/linkedql/steps/test-cases/collect.json
new file mode 100644
index 0000000..28346b9
--- /dev/null
+++ b/query/linkedql/steps/test-cases/collect.json
@@ -0,0 +1,34 @@
+{
+  "data": {
+    "http://example.com/friends": {
+      "@list": [
+        { "@id": "http://example.com/alice" },
+        { "@id": "http://example.com/bob" }
+      ]
+    }
+  },
+  "query": {
+    "@context": {
+      "linkedql": "http://cayley.io/linkedql"
+    },
+    "@type": "linkedql:Documents",
+    "linkedql:from": {
+      "@type": "linkedql:Collect",
+      "linkedql:name": "http://example.com/friends",
+      "linkedql:from": {
+        "@type": "linkedql:Vertex",
+        "linkedql:values": []
+      }
+    }
+  },
+  "results": [
+    {
+      "http://example.com/friends": {
+        "@list": [
+          { "@id": "http://example.com/alice" },
+          { "@id": "http://example.com/bob" }
+        ]
+      }
+    }
+  ]
+}