Repository: cayleygraph/cayley
Pull request #896 (2019-12-11 20:25:52)

Extracted Client
Code generated a client package with [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator) based on `docs/api/swagger.yml`. 

 - [x] Corrected types in `swagger.yml`
 - [x] Refactor HTTP Server tests to not rely on client

<!-- 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/896)
<!-- Reviewable:end -->


dennwc: As agreed on Slack, let's make it a separate package.

So let's do the following:
1. Create a new `cayleygraph/go-client` repository.
2. Initialize it with an empty/dummy `README`.
3. PR this code there.

Thanks!

Patch:
diff --git a/client/client.go b/client/client.go
index 09056e2..86782fa 100644
--- a/client/client.go
+++ b/client/client.go
@@ -14,6 +14,9 @@ func New(addr string) *Client {
 	return &Client{addr: addr, cli: http.DefaultClient}
 }
 
+// Client is a struct used for communicating with a Cayley server through HTTP
+// Deprecated: Client exists for backwards compatability. New code should use
+// the updated client github.com/cayleygraph/go-client
 type Client struct {
 	addr string
 	cli  *http.Client
diff --git a/cmd/cayley/cayley.go b/cmd/cayley/cayley.go
index 62d62bb..a13d4db 100644
--- a/cmd/cayley/cayley.go
+++ b/cmd/cayley/cayley.go
@@ -143,6 +143,7 @@ func init() {
 		command.NewConvertCmd(),
 		command.NewDedupCommand(),
 		command.NewHealthCmd(),
+		command.NewSchemaCommand(),
 	)
 	rootCmd.PersistentFlags().StringP("config", "c", "", "path to an explicit configuration file")
 
diff --git a/cmd/cayley/command/schema.go b/cmd/cayley/command/schema.go
new file mode 100644
index 0000000..bd3bc1f
--- /dev/null
+++ b/cmd/cayley/command/schema.go
@@ -0,0 +1,42 @@
+package command
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/cayleygraph/cayley/internal/linkedql/schema"
+)
+
+func NewSchemaCommand() *cobra.Command {
+	root := &cobra.Command{
+		Use:   "schema",
+		Short: "Commands related to RDF schema",
+	}
+	root.AddCommand(
+		NewLinkedQLSchemaCommand(),
+	)
+	return root
+}
+
+func NewLinkedQLSchemaCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:   "linkedql",
+		Short: "Generate LinkedQL Schema to stdout",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if len(args) > 0 {
+				return fmt.Errorf("too many arguments provided, expected 0")
+			}
+			data := schema.Generate()
+			buf := bytes.NewBuffer(nil)
+			err := json.Indent(buf, data, "", "\t")
+			if err != nil {
+				return err
+			}
+			fmt.Println(buf)
+			return nil
+		},
+	}
+}
diff --git a/docs/api/swagger.yml b/docs/api/swagger.yml
index 64c64e1..fdb63e7 100644
--- a/docs/api/swagger.yml
+++ b/docs/api/swagger.yml
@@ -329,9 +329,7 @@ paths:
           content:
             'application/json':
               schema:
-                oneOf:
-                  - type: "array"
-                  - type: "object"
+                $ref: '#/components/schemas/QueryResult'
         default:
           description: "Unexpected error"
           content:
@@ -376,9 +374,7 @@ paths:
           content:
             'application/json':
               schema:
-                oneOf:
-                - type: "array"
-                - type: "object"
+                $ref: '#/components/schemas/QueryResult'
         default:
           description: "Unexpected error"
           content:
@@ -450,6 +446,14 @@ paths:
                 $ref: '#/components/schemas/Error'
 components:
   schemas:
+    QueryResult:
+      type: object
+      properties:
+        result:
+          type: array
+          nullable: true
+          items:
+            type: object
     NQuads:
       type: "string"
       format: "binary"
diff --git a/docs/quickstart-as-application.md b/docs/quickstart-as-application.md
index 9c05c5d..ccd4abe 100644
--- a/docs/quickstart-as-application.md
+++ b/docs/quickstart-as-application.md
@@ -1,4 +1,4 @@
 # Quickstart-As-Application
 
-See [Getting Started](https://github.com/cayleygraph/cayley/blob/master/docs/Getting-Started.md) and [Installation](https://github.com/cayleygraph/cayley/blob/master/docs/Installation.md)
+See [Getting Started](https://github.com/cayleygraph/cayley/blob/master/docs/getting-started.md) and [Installation](https://github.com/cayleygraph/cayley/blob/master/docs/installation.md)
 
diff --git a/go.mod b/go.mod
index b3a5a89..58706e3 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
 	github.com/badgerodon/peg v0.0.0-20130729175151-9e5f7f4d07ca
-	github.com/cayleygraph/quad v1.1.0
+	github.com/cayleygraph/quad v1.2.1
 	github.com/cockroachdb/apd v1.1.0 // indirect
 	github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
 	github.com/coreos/bbolt v1.3.3 // indirect
diff --git a/go.sum b/go.sum
index a9f06dc..60c818b 100644
--- a/go.sum
+++ b/go.sum
@@ -27,6 +27,9 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/cayleygraph/quad v1.1.0 h1:w1nXAmn+nz07+qlw89dke9LwWkYpeX+OcvfTvGQRBpM=
 github.com/cayleygraph/quad v1.1.0/go.mod h1:maWODEekEhrO0mdc9h5n/oP7cH1h/OTgqQ2qWbuI9M4=
+github.com/cayleygraph/quad v1.2.0/go.mod h1:maWODEekEhrO0mdc9h5n/oP7cH1h/OTgqQ2qWbuI9M4=
+github.com/cayleygraph/quad v1.2.1 h1:PqqP+F8BLICaAWoGvSlrx1U11pXEc2BBodmTv30uSLM=
+github.com/cayleygraph/quad v1.2.1/go.mod h1:maWODEekEhrO0mdc9h5n/oP7cH1h/OTgqQ2qWbuI9M4=
 github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
diff --git a/inference/inference.go b/inference/inference.go
new file mode 100644
index 0000000..6e172cd
--- /dev/null
+++ b/inference/inference.go
@@ -0,0 +1,464 @@
+// Package inference implements an in-memory store for inference.
+//
+// RDFS Rules:
+//
+//		1. (x p y) -> (p rdf:type rdf:Property)
+//		2. (p rdfs:domain c), (x p y) -> (x rdf:type c)
+//		3. (p rdfs:range c), (x p y) -> (y rdf:type c)
+//		4a. (x p y) -> (x rdf:type rdfs:Resource)
+//		4b. (x p y) -> (y rdf:type rdfs:Resource)
+//		5. (p rdfs:subPropertyOf q), (q rdfs:subPropertyOf r) -> (p rdfs:subPropertyOf r)
+//		6. (p rdf:type Property) -> (p rdfs:subPropertyOf p)
+//		7. (p rdf:subPropertyOf q), (x p y) -> (x q y)
+//		8. (c rdf:type rdfs:Class) -> (c rdfs:subClassOf rdfs:Resource)
+//		9. (c rdfs:subClassOf d), (x rdf:type c) -> (x rdf:type d)
+//		10. (c rdf:type rdfs:Class) -> (c rdfs:subClassOf c)
+//		11. (c rdfs:subClassOf d), (d rdfs:subClassOf e) -> (c rdfs:subClassOf e)
+//		12. (p rdf:type rdfs:ContainerMembershipProperty) -> (p rdfs:subPropertyOf rdfs:member)
+//		13. (x rdf:type rdfs:Datatype) -> (x rdfs:subClassOf rdfs:Literal)
+//
+// Exported from: https://www.researchgate.net/figure/RDF-RDFS-entailment-rules_tbl1_268419911
+//
+// Implemented here: 1 2 3 5 6 8 10 11
+package inference
+
+import (
+	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/voc/rdf"
+	"github.com/cayleygraph/quad/voc/rdfs"
+)
+
+// classSet is a set of RDF Classes
+type classSet map[*Class]struct{}
+
+// propertySet is a set of RDF Properties
+type propertySet map[*Property]struct{}
+
+// Class represents a RDF Class with the links to classes and other properties
+type Class struct {
+	store      *Store
+	name       quad.Value
+	explicit   bool
+	references int
+	super      classSet
+	sub        classSet
+	ownProp    propertySet
+	inProp     propertySet
+}
+
+func (s *Store) newClass(name quad.Value, explicit bool) *Class {
+	c := &Class{
+		store:    s,
+		name:     name,
+		explicit: explicit,
+		super:    make(classSet),
+		sub:      make(classSet),
+		ownProp:  make(propertySet),
+		inProp:   make(propertySet),
+	}
+	s.classes[name] = c
+	return c
+}
+
+// Name returns the class's name
+func (c *Class) Name() quad.Value {
+	return c.name
+}
+
+// IsSubClassOf recursively checks whether class is a superClass
+func (c *Class) IsSubClassOf(super *Class) bool {
+	if c == super {
+		return true
+	}
+	if super.name == quad.IRI(rdfs.Resource) {
+		return true
+	}
+	if _, ok := c.super[super]; ok {
+		return true
+	}
+	for s := range c.super {
+		if s.IsSubClassOf(super) {
+			return true
+		}
+	}
+	return false
+}
+
+func (c *Class) isReferenced() bool {
+	return c.explicit || len(c.super) > 0 ||
+		len(c.sub) > 0 ||
+		len(c.ownProp) > 0 ||
+		len(c.inProp) > 0 ||
+		c.references > 0
+}
+
+func (c *Class) deleteIfUnreferenced() {
+	if c != nil && !c.isReferenced() {
+		c.store.deleteClass(c.name)
+	}
+}
+
+// Property represents a RDF Property with the links to classes and other properties
+type Property struct {
+	name       quad.Value
+	explicit   bool
+	references int
+	domain     *Class
+	prange     *Class
+	super      propertySet
+	sub        propertySet
+	store      *Store
+}
+
+func newProperty(name quad.Value, explicit bool, store *Store) *Property {
+	return &Property{
+		name:     name,
+		explicit: explicit,
+		super:    make(propertySet),
+		sub:      make(propertySet),
+		store:    store,
+	}
+}
+
+// Name returns the property's name
+func (p *Property) Name() quad.Value {
+	return p.name
+}
+
+// Domain returns the domain of the property
+func (p *Property) Domain() *Class {
+	return p.domain
+}
+
+// Range returns the range of the property
+func (p *Property) Range() *Class {
+	return p.prange
+}
+
+// IsSubPropertyOf recursively checks whether property is a superProperty
+func (p *Property) IsSubPropertyOf(super *Property) bool {
+	if p == super {
+		return true
+	}
+	if _, ok := p.super[super]; ok {
+		return true
+	}
+	for s := range p.super {
+		if s.IsSubPropertyOf(super) {
+			return true
+		}
+	}
+	return false
+}
+
+func (p *Property) isReferenced() bool {
+	return p.explicit || p.references > 0 ||
+		len(p.super) > 0 ||
+		len(p.sub) > 0 ||
+		p.domain != nil ||
+		p.prange != nil
+}
+
+func (p *Property) deleteIfUnreferenced() {
+	if p != nil && !p.isReferenced() {
+		p.store.deleteProperty(p.name)
+	}
+}
+
+// Store is a struct holding the inference data
+type Store struct {
+	classes    map[quad.Value]*Class
+	properties map[quad.Value]*Property
+}
+
+// NewStore creates a new Store
+func NewStore() Store {
+	s := Store{
+		classes:    make(map[quad.Value]*Class),
+		properties: make(map[quad.Value]*Property),
+	}
+	s.ensureClass(quad.IRI(rdfs.Resource))
+	return s
+}
+
+// GetClass returns a class struct for class name, if it doesn't exist in the store then it returns nil
+func (s *Store) GetClass(name quad.Value) *Class {
+	return s.classes[name]
+}
+
+// GetProperty returns a class struct for property name, if it doesn't exist in the store then it returns nil
+func (s *Store) GetProperty(name quad.Value) *Property {
+	return s.properties[name]
+}
+
+func (s *Store) ensureClass(name quad.Value) {
+	if c, ok := s.classes[name]; ok {
+		c.explicit = true
+	} else {
+		_ = s.newClass(name, true)
+	}
+}
+
+func (s *Store) getOrCreateImplicitClass(name quad.Value) *Class {
+	c, ok := s.classes[name]
+	if !ok {
+		c = s.newClass(name, false)
+	}
+	return c
+}
+
+func (s *Store) createProperty(name quad.Value) {
+	if property, ok := s.properties[name]; ok {
+		property.explicit = true
+		return
+	}
+	s.properties[name] = newProperty(name, true, s)
+}
+
+func (s *Store) getOrCreateImplicitProperty(name quad.Value) *Property {
+	if p, ok := s.properties[name]; ok {
+		return p
+	}
+	p := newProperty(name, false, s)
+	s.properties[name] = p
+	return p
+}
+
+func (s *Store) addClassRelationship(child quad.Value, parent quad.Value) {
+	p := s.getOrCreateImplicitClass(parent)
+	c := s.getOrCreateImplicitClass(child)
+	if _, ok := p.sub[c]; !ok {
+		p.sub[c] = struct{}{}
+		c.super[p] = struct{}{}
+	}
+}
+
+func (s *Store) addPropertyRelationship(child quad.Value, parent quad.Value) {
+	p := s.getOrCreateImplicitProperty(parent)
+	c := s.getOrCreateImplicitProperty(child)
+	if _, ok := p.sub[c]; !ok {
+		p.sub[c] = struct{}{}
+		c.super[p] = struct{}{}
+	}
+}
+
+func (s *Store) setPropertyDomain(property quad.Value, domain quad.Value) {
+	p := s.getOrCreateImplicitProperty(property)
+	c := s.getOrCreateImplicitClass(domain)
+	// FIXME(iddan): Currently doesn't support multiple domains as they are very rare
+	p.domain = c
+	c.ownProp[p] = struct{}{}
+}
+
+func (s *Store) setPropertyRange(property quad.Value, prange quad.Value) {
+	p := s.getOrCreateImplicitProperty(property)
+	c := s.getOrCreateImplicitClass(prange)
+	p.prange = c
+	// FIXME(iddan): Currently doesn't support multiple ranges as they are very rare
+	c.inProp[p] = struct{}{}
+}
+
+func (s *Store) addClassInstance(name quad.Value) {
+	c := s.GetClass(name)
+	if c == nil {
+		c = s.getOrCreateImplicitClass(name)
+	}
+	c.references++
+}
+
+func (s *Store) addPropertyInstance(name quad.Value) *Property {
+	p := s.GetProperty(name)
+	if p == nil {
+		p = s.getOrCreateImplicitProperty(name)
+	}
+	p.references++
+	return p
+}
+
+// ProcessQuads is used to update the store with multiple quads
+func (s *Store) ProcessQuads(quads ...quad.Quad) {
+	for _, q := range quads {
+		s.processQuad(q)
+	}
+}
+
+// processQuad is used to update the store with a new quad
+func (s *Store) processQuad(q quad.Quad) {
+	pred, ok := q.Predicate.(quad.IRI)
+	if !ok {
+		return
+	}
+	sub, obj := q.Subject, q.Object
+	switch pred {
+	case rdf.Type:
+		switch obj := obj.(type) {
+		case quad.BNode:
+			s.addClassInstance(obj)
+		case quad.IRI:
+			switch obj {
+			case rdfs.Class:
+				s.ensureClass(sub)
+			case rdf.Property:
+				s.createProperty(sub)
+			default:
+				s.addClassInstance(obj)
+			}
+		}
+	case rdfs.SubPropertyOf:
+		s.addPropertyRelationship(sub, obj)
+	case rdfs.SubClassOf:
+		s.addClassRelationship(sub, obj)
+	case rdfs.Domain:
+		s.setPropertyDomain(sub, obj)
+	case rdfs.Range:
+		s.setPropertyRange(sub, obj)
+	default:
+		p := s.addPropertyInstance(pred)
+		domain := p.Domain()
+		if domain != nil {
+			domain.references++
+		}
+		prange := p.Range()
+		if prange != nil {
+			prange.references++
+		}
+	}
+}
+
+func (s *Store) deleteClass(name quad.Value) {
+	c, ok := s.classes[name]
+	if !ok {
+		return
+	}
+	for sub := range c.sub {
+		delete(sub.super, c)
+	}
+	for super := range c.super {
+		delete(super.sub, c)
+	}
+	delete(s.classes, name)
+}
+
+func (s *Store) deleteProperty(name quad.Value) {
+	p, ok := s.properties[name]
+	if !ok {
+		return
+	}
+	for super := range p.super {
+		delete(super.sub, p)
+	}
+	for sub := range p.sub {
+		delete(sub.super, p)
+	}
+	delete(s.properties, name)
+}
+
+func (s *Store) deleteClassRel(child quad.Value, parent quad.Value) {
+	p := s.GetClass(parent)
+	c := s.GetClass(child)
+	if _, ok := p.sub[c]; ok {
+		delete(p.sub, c)
+		delete(c.super, p)
+		p.deleteIfUnreferenced()
+		c.deleteIfUnreferenced()
+	}
+}
+
+func (s *Store) deletePropertyRel(child quad.Value, parent quad.Value) {
+	p := s.GetProperty(parent)
+	c := s.GetProperty(child)
+	if _, ok := p.sub[c]; ok {
+		delete(p.sub, c)
+		delete(c.super, p)
+		p.deleteIfUnreferenced()
+		c.deleteIfUnreferenced()
+	}
+}
+
+func (s *Store) unsetPropertyDomain(property quad.Value, domain quad.Value) {
+	p := s.GetProperty(property)
+	c := s.GetClass(domain)
+	// FIXME(iddan): Currently doesn't support multiple domains as they are very rare
+	p.domain = nil
+	delete(c.ownProp, p)
+	p.deleteIfUnreferenced()
+	c.deleteIfUnreferenced()
+}
+
+func (s *Store) unsetPropertyRange(property quad.Value, prange quad.Value) {
+	p := s.GetProperty(property)
+	c := s.GetClass(prange)
+	p.prange = nil
+	// FIXME(iddan): Currently doesn't support multiple ranges as they are very rare
+	delete(c.inProp, p)
+	p.deleteIfUnreferenced()
+	c.deleteIfUnreferenced()
+}
+
+func (s *Store) deleteClassInstance(name quad.Value) {
+	c := s.GetClass(name)
+	if c == nil {
+		return
+	}
+	c.references--
+	c.deleteIfUnreferenced()
+}
+
+func (s *Store) deletePropertyInstance(name quad.Value) *Property {
+	p := s.GetProperty(name)
+	if p == nil {
+		return nil
+	}
+	p.references--
+	p.deleteIfUnreferenced()
+	return p
+}
+
+// UnprocessQuads is used to delete multiple quads from the store
+func (s *Store) UnprocessQuads(quads ...quad.Quad) {
+	for _, q := range quads {
+		s.unprocessQuad(q)
+	}
+}
+
+// unprocessQuad is used to delete a quad from the store
+func (s *Store) unprocessQuad(q quad.Quad) {
+	pred, ok := q.Predicate.(quad.IRI)
+	if !ok {
+		return
+	}
+	sub, obj := q.Subject, q.Object
+	switch pred {
+	case rdf.Type:
+		obj, ok := obj.(quad.IRI)
+		if !ok {
+			return
+		}
+		switch obj {
+		case rdfs.Class:
+			s.deleteClass(sub)
+		case rdf.Property:
+			s.deleteProperty(sub)
+		default:
+			s.deleteClassInstance(obj)
+		}
+	case rdfs.SubPropertyOf:
+		s.deletePropertyRel(sub, obj)
+	case rdfs.SubClassOf:
+		s.deleteClassRel(sub, obj)
+	case rdfs.Domain:
+		s.unsetPropertyDomain(sub, obj)
+	case rdfs.Range:
+		s.unsetPropertyRange(sub, obj)
+	default:
+		p := s.deletePropertyInstance(pred)
+		if p != nil {
+			if domain := p.Domain(); domain != nil {
+				s.deleteClassInstance(domain.Name())
+			}
+			if prange := p.Range(); prange != nil {
+				s.deleteClassInstance(prange.Name())
+			}
+		}
+	}
+}
diff --git a/internal/linkedql/schema/schema.go b/internal/linkedql/schema/schema.go
new file mode 100644
index 0000000..7d8d128
--- /dev/null
+++ b/internal/linkedql/schema/schema.go
@@ -0,0 +1,307 @@
+package schema
+
+import (
+	"encoding/json"
+	"reflect"
+
+	"github.com/cayleygraph/cayley/query/linkedql"
+	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/voc/owl"
+	"github.com/cayleygraph/quad/voc/rdf"
+	"github.com/cayleygraph/quad/voc/rdfs"
+	"github.com/cayleygraph/quad/voc/xsd"
+)
+
+var (
+	pathStep         = reflect.TypeOf((*linkedql.PathStep)(nil)).Elem()
+	iteratorStep     = reflect.TypeOf((*linkedql.IteratorStep)(nil)).Elem()
+	entityIdentifier = reflect.TypeOf((*linkedql.EntityIdentifier)(nil)).Elem()
+	value            = reflect.TypeOf((*quad.Value)(nil)).Elem()
+	operator         = reflect.TypeOf((*linkedql.Operator)(nil)).Elem()
+	propertyPath     = reflect.TypeOf((*linkedql.PropertyPath)(nil)).Elem()
+)
+
+func typeToRange(t reflect.Type) string {
+	if t.Kind() == reflect.Slice {
+		return typeToRange(t.Elem())
+	}
+	if t.Kind() == reflect.String {
+		return xsd.String
+	}
+	if t.Kind() == reflect.Bool {
+		return xsd.Boolean
+	}
+	if kind := t.Kind(); kind == reflect.Int64 || kind == reflect.Int {
+		return xsd.Int
+	}
+	if t.Implements(pathStep) {
+		return linkedql.Prefix + "PathStep"
+	}
+	if t.Implements(operator) {
+		return linkedql.Prefix + "Operator"
+	}
+	if t.Implements(value) {
+		return rdfs.Resource
+	}
+	if t.Implements(entityIdentifier) {
+		return owl.Thing
+	}
+	if t == propertyPath {
+		return linkedql.Prefix + "PropertyPath"
+	}
+	panic("Unexpected type " + t.String())
+}
+
+// identified is used for referencing a type
+type identified struct {
+	ID string `json:"@id"`
+}
+
+// newIdentified creates new identified struct
+func newIdentified(id string) identified {
+	return identified{ID: id}
+}
+
+// cardinalityRestriction is used to indicate a how many values can a property get
+type cardinalityRestriction struct {
+	ID          string     `json:"@id"`
+	Type        string     `json:"@type"`
+	Cardinality int        `json:"owl:cardinality"`
+	Property    identified `json:"owl:onProperty"`
+}
+
+func newBlankNodeID() string {
+	return quad.RandomBlankNode().String()
+}
+
+// newSingleCardinalityRestriction creates a cardinality of 1 restriction for given property
+func newSingleCardinalityRestriction(prop string) cardinalityRestriction {
+	return cardinalityRestriction{
+		ID:          newBlankNodeID(),
+		Type:        owl.Restriction,
+		Cardinality: 1,
+		Property:    identified{ID: prop},
+	}
+}
+
+// getOWLPropertyType for given kind of value type returns property OWL type
+func getOWLPropertyType(kind reflect.Kind) string {
+	if kind == reflect.String || kind == reflect.Bool || kind == reflect.Int64 || kind == reflect.Int {
+		return owl.DatatypeProperty
+	}
+	return owl.ObjectProperty
+}
+
+// property is used to declare a property
+type property struct {
+	ID     string      `json:"@id"`
+	Type   string      `json:"@type"`
+	Domain interface{} `json:"rdfs:domain"`
+	Range  interface{} `json:"rdfs:range"`
+}
+
+// class is used to declare a class
+type class struct {
+	ID           string        `json:"@id"`
+	Type         string        `json:"@type"`
+	Comment      string        `json:"rdfs:comment"`
+	SuperClasses []interface{} `json:"rdfs:subClassOf"`
+}
+
+// newClass creates a new class struct
+func newClass(id string, superClasses []interface{}, comment string) class {
+	return class{
+		ID:           id,
+		Type:         rdfs.Class,
+		SuperClasses: superClasses,
+		Comment:      comment,
+	}
+}
+
+// getStepTypeClasses for given step type returns the matching class identifiers
+func getStepTypeClasses(t reflect.Type) []string {
+	var typeClasses []string
+	if t.Implements(pathStep) {
+		typeClasses = append(typeClasses, linkedql.Prefix+"PathStep")
+	}
+	if t.Implements(iteratorStep) {
+		typeClasses = append(typeClasses, linkedql.Prefix+"IteratorStep")
+	}
+	return typeClasses
+}
+
+type list struct {
+	Members []interface{} `json:"@list"`
+}
+
+func newList(members []interface{}) list {
+	return list{
+		Members: members,
+	}
+}
+
+type unionOf struct {
+	ID   string `json:"@id"`
+	Type string `json:"@type"`
+	List list   `json:"owl:unionOf"`
+}
+
+func newUnionOf(classes []string) unionOf {
+	var members []interface{}
+	for _, class := range classes {
+		members = append(members, newIdentified(class))
+	}
+	return unionOf{
+		ID:   newBlankNodeID(),
+		Type: owl.Class,
+		List: newList(members),
+	}
+}
+
+func newGenerator() *generator {
+	return &generator{
+		propToTypes:   make(map[string]map[string]struct{}),
+		propToDomains: make(map[string]map[string]struct{}),
+		propToRanges:  make(map[string]map[string]struct{}),
+	}
+}
+
+type generator struct {
+	out           []interface{}
+	propToTypes   map[string]map[string]struct{}
+	propToDomains map[string]map[string]struct{}
+	propToRanges  map[string]map[string]struct{}
+}
+
+// returns super types
+func (g *generator) addTypeFields(name string, t reflect.Type, indirect bool) []interface{} {
+	var super []interface{}
+	for j := 0; j < t.NumField(); j++ {
+		f := t.Field(j)
+		if f.Anonymous {
+			if f.Type.Kind() != reflect.Struct || !indirect {
+				continue
+			}
+			super = append(super, g.addTypeFields(name, f.Type, false)...)
+			continue
+		}
+		prop := linkedql.Prefix + f.Tag.Get("json")
+		if f.Type.Kind() != reflect.Slice {
+			super = append(super, newSingleCardinalityRestriction(prop))
+		}
+		typ := getOWLPropertyType(f.Type.Kind())
+
+		if g.propToTypes[prop] == nil {
+			g.propToTypes[prop] = make(map[string]struct{})
+		}
+		g.propToTypes[prop][typ] = struct{}{}
+
+		if g.propToDomains[prop] == nil {
+			g.propToDomains[prop] = make(map[string]struct{})
+		}
+		g.propToDomains[prop][name] = struct{}{}
+
+		if g.propToRanges[prop] == nil {
+			g.propToRanges[prop] = make(map[string]struct{})
+		}
+		g.propToRanges[prop][typeToRange(f.Type)] = struct{}{}
+	}
+	return super
+}
+
+func (g *generator) AddType(name string, t reflect.Type) {
+	step, ok := reflect.New(t).Interface().(linkedql.Step)
+	if !ok {
+		return
+	}
+	var super []interface{}
+	stepTypeClasses := getStepTypeClasses(reflect.PtrTo(t))
+	for _, typeClass := range stepTypeClasses {
+		super = append(super, newIdentified(typeClass))
+	}
+	super = append(super, g.addTypeFields(name, t, true)...)
+	g.out = append(g.out, newClass(name, super, step.Description()))
+}
+
+func (g *generator) Generate() []byte {
+	for prop, types := range g.propToTypes {
+		if len(types) != 1 {
+			panic("Properties must be either object properties or datatype properties. " + prop + " has both.")
+		}
+		var typ string
+		for t := range types {
+			typ = t
+			break
+		}
+		var domains []string
+		for d := range g.propToDomains[prop] {
+			domains = append(domains, d)
+		}
+		var ranges []string
+		for r := range g.propToRanges[prop] {
+			ranges = append(ranges, r)
+		}
+		var dom interface{}
+		if len(domains) == 1 {
+			dom = identified{domains[0]}
+		} else {
+			dom = newUnionOf(domains)
+		}
+		var rng interface{}
+		if len(ranges) == 1 {
+			rng = newIdentified(ranges[0])
+		} else {
+			rng = newUnionOf(ranges)
+		}
+		g.out = append(g.out, property{
+			ID:     prop,
+			Type:   typ,
+			Domain: dom,
+			Range:  rng,
+		})
+	}
+	graph := []interface{}{
+		map[string]string{
+			"@id":   linkedql.Prefix + "Step",
+			"@type": owl.Class,
+		},
+		map[string]interface{}{
+			"@id":           linkedql.Prefix + "PathStep",
+			"@type":         owl.Class,
+			rdfs.SubClassOf: identified{ID: linkedql.Prefix + "Step"},
+		},
+		map[string]interface{}{
+			"@id":           linkedql.Prefix + "IteratorStep",
+			"@type":         owl.Class,
+			rdfs.SubClassOf: identified{ID: linkedql.Prefix + "Step"},
+		},
+	}
+	graph = append(graph, g.out...)
+	data, err := json.Marshal(map[string]interface{}{
+		"@context": map[string]interface{}{
+			"rdf":      rdf.NS,
+			"rdfs":     rdfs.NS,
+			"owl":      owl.NS,
+			"xsd":      xsd.NS,
+			"linkedql": linkedql.Namespace,
+		},
+		"@graph": graph,
+	})
+	if err != nil {
+		panic(err)
+	}
+	return data
+}
+
+// Generate a schema in JSON-LD format that contains all registered LinkedQL types and properties.
+func Generate() []byte {
+	g := newGenerator()
+	for _, name := range linkedql.RegisteredTypes() {
+		t, ok := linkedql.TypeByName(name)
+		if !ok {
+			panic("type is registered, but the lookup fails")
+		}
+		g.AddType(name, t)
+	}
+	return g.Generate()
+}
diff --git a/query/linkedql/errors.go b/query/linkedql/errors.go
new file mode 100644
index 0000000..7b0e4ac
--- /dev/null
+++ b/query/linkedql/errors.go
@@ -0,0 +1,11 @@
+package linkedql
+
+import "fmt"
+
+func formatMultiError(errors []error) error {
+	joinedErr := ""
+	for _, err := range errors {
+		joinedErr += "; " + err.Error()
+	}
+	return fmt.Errorf("Could not parse PropertyPath: %v", joinedErr)
+}
diff --git a/query/linkedql/iter_docs.go b/query/linkedql/iter_docs.go
new file mode 100644
index 0000000..5d15d29
--- /dev/null
+++ b/query/linkedql/iter_docs.go
@@ -0,0 +1,92 @@
+package linkedql
+
+import (
+	"context"
+
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/quad"
+)
+
+var _ query.Iterator = (*DocumentIterator)(nil)
+
+type document = map[string]interface{}
+type properties = map[string][]interface{}
+type idToProperties = map[quad.Value]properties
+
+// DocumentIterator is an iterator of documents from the graph
+type DocumentIterator struct {
+	tagsIt     *TagsIterator
+	ids        []quad.Value
+	properties idToProperties
+	current    int
+}
+
+// NewDocumentIterator returns a new DocumentIterator for a QuadStore and Path.
+func NewDocumentIterator(valueIt *ValueIterator) *DocumentIterator {
+	tagsIt := &TagsIterator{valueIt: valueIt, selected: nil}
+	return &DocumentIterator{tagsIt: tagsIt, current: -1}
+}
+
+// Next implements query.Iterator.
+func (it *DocumentIterator) Next(ctx context.Context) bool {
+	if it.properties == nil {
+		it.properties = make(idToProperties)
+		for it.tagsIt.Next(ctx) {
+			id := it.tagsIt.valueIt.Value()
+			tags := it.tagsIt.getTags()
+			it.ids = append(it.ids, id)
+			for k, v := range tags {
+				m, ok := it.properties[id]
+				if !ok {
+					m = make(properties)
+					it.properties[id] = m
+				}
+				m[k] = append(m[k], v)
+			}
+		}
+	}
+	if it.current < len(it.ids)-1 {
+		it.current++
+		return true
+	}
+	return false
+}
+
+// Result implements query.Iterator.
+func (it *DocumentIterator) Result() interface{} {
+	if it.current >= len(it.ids) {
+		return nil
+	}
+	id := it.ids[it.current]
+	// FIXME(iddan): don't cast to string when collation is Raw
+	var sid string
+	switch val := id.(type) {
+	case quad.IRI:
+		sid = string(val)
+	case quad.BNode:
+		sid = val.String()
+	}
+	d := document{
+		"@id": sid,
+	}
+	for k, v := range it.properties[id] {
+		d[k] = v
+	}
+	return d
+}
+
+// Err implements query.Iterator.
+func (it *DocumentIterator) Err() error {
+	if it.tagsIt == nil {
+		return nil
+	}
+	return it.tagsIt.Err()
+}
+
+// Close implements query.Iterator.
+func (it *DocumentIterator) Close() error {
+	if it.tagsIt == nil {
+		return nil
+	}
+	return it.tagsIt.Close()
+}
diff --git a/query/linkedql/iter_tags.go b/query/linkedql/iter_tags.go
new file mode 100644
index 0000000..a4668af
--- /dev/null
+++ b/query/linkedql/iter_tags.go
@@ -0,0 +1,57 @@
+package linkedql
+
+import (
+	"context"
+
+	"github.com/cayleygraph/cayley/graph/refs"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/quad/jsonld"
+)
+
+var _ query.Iterator = (*TagsIterator)(nil)
+
+// TagsIterator is a result iterator for records consisting of selected tags
+// or all the tags in the query.
+type TagsIterator struct {
+	valueIt  *ValueIterator
+	selected []string
+}
+
+// Next implements query.Iterator.
+func (it *TagsIterator) Next(ctx context.Context) bool {
+	return it.valueIt.Next(ctx)
+}
+
+func (it *TagsIterator) getTags() map[string]interface{} {
+	refTags := make(map[string]refs.Ref)
+	it.valueIt.scanner.TagResults(refTags)
+
+	tags := make(map[string]interface{})
+	// FIXME(iddan): only convert when collation is JSON/JSON-LD, leave as Ref otherwise
+	if it.selected != nil {
+		for _, tag := range it.selected {
+			tags[tag] = jsonld.FromValue(it.valueIt.getName(refTags[tag]))
+		}
+	} else {
+		for tag, ref := range refTags {
+			tags[tag] = jsonld.FromValue(it.valueIt.getName(ref))
+		}
+	}
+
+	return tags
+}
+
+// Result implements query.Iterator.
+func (it *TagsIterator) Result() interface{} {
+	return it.getTags()
+}
+
+// Err implements query.Iterator.
+func (it *TagsIterator) Err() error {
+	return it.valueIt.Err()
+}
+
+// Close implements query.Iterator.
+func (it *TagsIterator) Close() error {
+	return it.valueIt.Close()
+}
diff --git a/query/linkedql/iter_values.go b/query/linkedql/iter_values.go
new file mode 100644
index 0000000..9f926e5
--- /dev/null
+++ b/query/linkedql/iter_values.go
@@ -0,0 +1,80 @@
+package linkedql
+
+import (
+	"context"
+
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/graph/iterator"
+	"github.com/cayleygraph/cayley/graph/refs"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/jsonld"
+)
+
+var _ query.Iterator = (*ValueIterator)(nil)
+
+// ValueIterator is an iterator of values from the graph.
+type ValueIterator struct {
+	namer   refs.Namer
+	path    *path.Path
+	scanner iterator.Scanner
+}
+
+// NewValueIterator returns a new ValueIterator for a path and namer.
+func NewValueIterator(p *path.Path, namer refs.Namer) *ValueIterator {
+	return &ValueIterator{namer: namer, path: p}
+}
+
+// NewValueIteratorFromPathStep attempts to build a path from PathStep and return a new ValueIterator of it.
+// If BuildPath fails returns error.
+func NewValueIteratorFromPathStep(step PathStep, qs graph.QuadStore) (*ValueIterator, error) {
+	p, err := step.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return NewValueIterator(p, qs), nil
+}
+
+// Next implements query.Iterator.
+func (it *ValueIterator) Next(ctx context.Context) bool {
+	if it.scanner == nil {
+		it.scanner = it.path.BuildIterator(ctx).Iterate()
+	}
+	return it.scanner.Next(ctx)
+}
+
+func (it *ValueIterator) getName(ref refs.Ref) quad.Value {
+	name := it.namer.NameOf(ref)
+	return name
+}
+
+// Value returns the current value
+func (it *ValueIterator) Value() quad.Value {
+	if it.scanner == nil {
+		return nil
+	}
+	return it.getName(it.scanner.Result())
+}
+
+// Result implements query.Iterator.
+func (it *ValueIterator) Result() interface{} {
+	// FIXME(iddan): only convert when collation is JSON/JSON-LD, leave as Ref otherwise
+	return jsonld.FromValue(it.Value())
+}
+
+// Err implements query.Iterator.
+func (it *ValueIterator) Err() error {
+	if it.scanner == nil {
+		return nil
+	}
+	return it.scanner.Err()
+}
+
+// Close implements query.Iterator.
+func (it *ValueIterator) Close() error {
+	if it.scanner == nil {
+		return nil
+	}
+	return it.scanner.Close()
+}
diff --git a/query/linkedql/linkedql.go b/query/linkedql/linkedql.go
new file mode 100644
index 0000000..7efdf72
--- /dev/null
+++ b/query/linkedql/linkedql.go
@@ -0,0 +1,58 @@
+package linkedql
+
+import (
+	"context"
+	"errors"
+
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/quad/voc"
+)
+
+const (
+	// Name is the name exposed to the query interface.
+	Name = "linkedql"
+	// Namespace is an RDF namespace used for LinkedQL classes.
+	Namespace = "http://cayley.io/linkedql#"
+	// Prefix is an RDF namespace prefix used for LinkedQL classes.
+	Prefix = "linkedql:"
+)
+
+func init() {
+	// IRI namespace support
+	voc.Register(voc.Namespace{Full: Namespace, Prefix: Prefix})
+	// register the language
+	query.RegisterLanguage(query.Language{
+		Name: Name,
+		Session: func(qs graph.QuadStore) query.Session {
+			return NewSession(qs)
+		},
+	})
+}
+
+var _ query.Session = &Session{}
+
+// Session represents a LinkedQL query processing.
+type Session struct {
+	qs graph.QuadStore
+}
+
+// NewSession creates a new Session.
+func NewSession(qs graph.QuadStore) *Session {
+	return &Session{
+		qs: qs,
+	}
+}
+
+// Execute for a given context, query and options return an iterator of results.
+func (s *Session) Execute(ctx context.Context, query string, opt query.Options) (query.Iterator, error) {
+	item, err := Unmarshal([]byte(query))
+	if err != nil {
+		return nil, err
+	}
+	step, ok := item.(IteratorStep)
+	if !ok {
+		return nil, errors.New("must execute a valid step")
+	}
+	return step.BuildIterator(s.qs)
+}
diff --git a/query/linkedql/operators.go b/query/linkedql/operators.go
new file mode 100644
index 0000000..c5e22e7
--- /dev/null
+++ b/query/linkedql/operators.go
@@ -0,0 +1,67 @@
+package linkedql
+
+import (
+	"regexp"
+
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/cayley/query/shape"
+	"github.com/cayleygraph/quad"
+)
+
+// Operator represents an operator used in a query inside a step (e.g. greater than).
+type Operator interface {
+	RegistryItem
+	Apply(p *path.Path) (*path.Path, error)
+}
+
+var _ Operator = (*RegExp)(nil)
+
+// RegExp corresponds to regex().
+type RegExp struct {
+	Pattern     string `json:"pattern"`
+	IncludeIRIs bool   `json:"includeIRIs,omitempty"`
+}
+
+// Type implements Operator.
+func (s *RegExp) Type() quad.IRI {
+	return Prefix + "RegExp"
+}
+
+// Description implements Operator.
+func (s *RegExp) Description() string {
+	return "RegExp filters out values that do not match given pattern. If includeIRIs is set to true it matches IRIs in addition to literals."
+}
+
+// Apply implements Operator.
+func (s *RegExp) Apply(p *path.Path) (*path.Path, error) {
+	pattern, err := regexp.Compile(s.Pattern)
+	if err != nil {
+		return nil, err
+	}
+	if s.IncludeIRIs {
+		return p.RegexWithRefs(pattern), nil
+	}
+	return p.RegexWithRefs(pattern), nil
+}
+
+var _ Operator = (*Like)(nil)
+
+// Like corresponds to like().
+type Like struct {
+	Pattern string `json:"pattern"`
+}
+
+// Type implements Operator.
+func (s *Like) Type() quad.IRI {
+	return Prefix + "Like"
+}
+
+// Description implements Operator.
+func (s *Like) Description() string {
+	return "Like filters out values that do not match given pattern."
+}
+
+// Apply implements Operator.
+func (s *Like) Apply(p *path.Path) (*path.Path, error) {
+	return p.Filters(shape.Wildcard{Pattern: s.Pattern}), nil
+}
diff --git a/query/linkedql/property_path.go b/query/linkedql/property_path.go
new file mode 100644
index 0000000..c26d99f
--- /dev/null
+++ b/query/linkedql/property_path.go
@@ -0,0 +1,114 @@
+package linkedql
+
+import (
+	"encoding/json"
+
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/quad"
+)
+
+type propertyPathI interface {
+	BuildPath(qs graph.QuadStore) (*path.Path, error)
+}
+
+// PropertyPath is an interface to be used where a path of properties is expected.
+type PropertyPath struct {
+	p propertyPathI
+}
+
+// Type implements Step
+func (*PropertyPath) Type() string {
+	return Prefix + "PropertyPath"
+}
+
+// Description implements Step.
+func (*PropertyPath) Description() string {
+	return "PropertyPath is a string, multiple strins or path describing a set of properties"
+}
+
+func (p *PropertyPath) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	return p.p.BuildPath(qs)
+}
+
+// UnmarshalJSON implements RawMessage
+func (p *PropertyPath) UnmarshalJSON(data []byte) error {
+	var errors []error
+
+	var propertyIRIs PropertyIRIs
+	err := json.Unmarshal(data, &propertyIRIs)
+	if err == nil {
+		p.p = propertyIRIs
+		return nil
+	}
+	errors = append(errors, err)
+
+	var propertyIRIStrings PropertyIRIStrings
+	err = json.Unmarshal(data, &propertyIRIStrings)
+	if err == nil {
+		p.p = propertyIRIStrings
+		return nil
+	}
+	errors = append(errors, err)
+
+	var propertyIRI PropertyIRI
+	err = json.Unmarshal(data, &propertyIRI)
+	if err == nil {
+		p.p = propertyIRI
+		return nil
+	}
+	errors = append(errors, err)
+
+	var propertyIRIString PropertyIRIString
+	err = json.Unmarshal(data, &propertyIRIString)
+	if err == nil {
+		p.p = propertyIRIString
+		return nil
+	}
+	errors = append(errors, err)
+
+	return formatMultiError(errors)
+}
+
+// PropertyIRIs is a slice of property IRIs.
+type PropertyIRIs []quad.IRI
+
+// BuildPath implements PropertyPath.
+func (p PropertyIRIs) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	var values []quad.Value
+	for _, iri := range p {
+		values = append(values, iri)
+	}
+	vertex := &Vertex{Values: values}
+	return vertex.BuildPath(qs)
+}
+
+// PropertyIRIStrings is a slice of property IRI strings.
+type PropertyIRIStrings []string
+
+// BuildPath implements PropertyPath.
+func (p PropertyIRIStrings) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	var iris PropertyIRIs
+	for _, iri := range p {
+		iris = append(iris, quad.IRI(iri))
+	}
+	return iris.BuildPath(qs)
+}
+
+// PropertyIRI is an IRI of a Property
+type PropertyIRI quad.IRI
+
+// BuildPath implements PropertyPath
+func (p PropertyIRI) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	vertex := &Vertex{Values: []quad.Value{quad.IRI(p)}}
+	return vertex.BuildPath(qs)
+}
+
+// PropertyIRIString is a string of IRI of a Property
+type PropertyIRIString string
+
+// BuildPath implements PropertyPath
+func (p PropertyIRIString) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	iri := PropertyIRI(p)
+	return iri.BuildPath(qs)
+}
diff --git a/query/linkedql/registry.go b/query/linkedql/registry.go
new file mode 100644
index 0000000..4e12e67
--- /dev/null
+++ b/query/linkedql/registry.go
@@ -0,0 +1,271 @@
+package linkedql
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/cayleygraph/quad"
+)
+
+var (
+	typeByName = make(map[string]reflect.Type)
+	nameByType = make(map[reflect.Type]string)
+)
+
+// TypeByName returns a type by its registration name. See Register.
+func TypeByName(name string) (reflect.Type, bool) {
+	t, ok := typeByName[name]
+	return t, ok
+}
+
+// Registered types returns type names of all registered types.
+func RegisteredTypes() []string {
+	out := make([]string, 0, len(typeByName))
+	for k := range typeByName {
+		out = append(out, k)
+	}
+	return out
+}
+
+// RegistryItem in the registry.
+type RegistryItem interface {
+	Type() quad.IRI
+	Description() string
+}
+
+// Register adds an Item type to the registry.
+func Register(typ RegistryItem) {
+	tp := reflect.TypeOf(typ)
+	if tp.Kind() == reflect.Ptr {
+		tp = tp.Elem()
+	}
+	if tp.Kind() != reflect.Struct {
+		panic("only structs are allowed")
+	}
+	name := string(typ.Type())
+	if _, ok := typeByName[name]; ok {
+		panic("this name was already registered")
+	}
+	typeByName[name] = tp
+	nameByType[tp] = name
+}
+
+var (
+	quadValue      = reflect.TypeOf((*quad.Value)(nil)).Elem()
+	quadSliceValue = reflect.TypeOf([]quad.Value{})
+	quadIRI        = reflect.TypeOf(quad.IRI(""))
+	quadSliceIRI   = reflect.TypeOf([]quad.IRI{})
+)
+
+// Unmarshal attempts to unmarshal an Item or returns error.
+func Unmarshal(data []byte) (RegistryItem, error) {
+	// TODO: make it a part of quad/jsonld package.
+	var m map[string]json.RawMessage
+	if err := json.Unmarshal(data, &m); err != nil {
+		return nil, err
+	}
+	var typ string
+	if err := json.Unmarshal(m["@type"], &typ); err != nil {
+		return nil, err
+	}
+	delete(m, "@type")
+	tp, ok := TypeByName(typ)
+	if !ok {
+		return nil, fmt.Errorf("unsupported item: %q", typ)
+	}
+	item := reflect.New(tp).Elem()
+	for i := 0; i < tp.NumField(); i++ {
+		f := tp.Field(i)
+		name := f.Name
+		tag := strings.SplitN(f.Tag.Get("json"), ",", 2)[0]
+		if tag == "-" {
+			continue
+		} else if tag != "" {
+			name = Prefix + tag
+		}
+		v, ok := m[name]
+		if !ok {
+			continue
+		}
+		fv := item.Field(i)
+		switch f.Type {
+		case quadValue:
+			var a interface{}
+			err := json.Unmarshal(v, &a)
+			if err != nil {
+				return nil, err
+			}
+			value, err := parseValue(v)
+			if err != nil {
+				return nil, err
+			}
+			fv.Set(reflect.ValueOf(value))
+			continue
+		case quadSliceValue:
+			var a []interface{}
+			err := json.Unmarshal(v, &a)
+			if err != nil {
+				return nil, err
+			}
+			var values []quad.Value
+			for _, item := range a {
+				value, err := parseValue(item)
+				if err != nil {
+					return nil, err
+				}
+				values = append(values, value)
+			}
+			fv.Set(reflect.ValueOf(values))
+			continue
+		case quadIRI:
+			var a interface{}
+			err := json.Unmarshal(v, &a)
+			if err != nil {
+				return nil, err
+			}
+			s, ok := a.(string)
+			if !ok {
+				return nil, fmt.Errorf("Expected a string but received %v instead", a)
+			}
+			val, err := parseIRI(s)
+			if err != nil {
+				return nil, err
+			}
+			fv.Set(reflect.ValueOf(val))
+			continue
+		case quadSliceIRI:
+			var a []interface{}
+			err := json.Unmarshal(v, &a)
+			if err != nil {
+				return nil, err
+			}
+			var values []quad.IRI
+			for _, item := range a {
+				s, ok := item.(string)
+				if !ok {
+					return nil, fmt.Errorf("Expected a string but received %v instead", item)
+				}
+				val, err := parseIRI(s)
+				if err != nil {
+					return nil, err
+				}
+				values = append(values, val)
+			}
+			fv.Set(reflect.ValueOf(values))
+			continue
+		}
+		switch f.Type.Kind() {
+		case reflect.Interface:
+			s, err := Unmarshal(v)
+			if err != nil {
+				return nil, err
+			}
+			fv.Set(reflect.ValueOf(s))
+		case reflect.Slice:
+			el := f.Type.Elem()
+			if el.Kind() != reflect.Interface {
+				err := json.Unmarshal(v, fv.Addr().Interface())
+				if err != nil {
+					return nil, err
+				}
+			} else {
+				var arr []json.RawMessage
+				if err := json.Unmarshal(v, &arr); err != nil {
+					return nil, err
+				}
+				if arr != nil {
+					va := reflect.MakeSlice(f.Type, len(arr), len(arr))
+					for i, v := range arr {
+						s, err := Unmarshal(v)
+						if err != nil {
+							return nil, err
+						}
+						va.Index(i).Set(reflect.ValueOf(s))
+					}
+					fv.Set(va)
+				}
+			}
+		default:
+			err := json.Unmarshal(v, fv.Addr().Interface())
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return item.Addr().Interface().(RegistryItem), nil
+}
+
+func parseBNode(s string) (quad.BNode, error) {
+	if !strings.HasPrefix(s, "_:") {
+		return "", fmt.Errorf("blank node ID must start with \"_:\"")
+	}
+	return quad.BNode(s[2:]), nil
+}
+
+func parseIRI(s string) (quad.IRI, error) {
+	return quad.IRI(s), nil
+}
+
+func parseIdentifier(s string) (quad.Value, error) {
+	bnode, err := parseBNode(s)
+	if err == nil {
+		return bnode, nil
+	}
+	iri, err := parseIRI(s)
+	if err == nil {
+		return iri, nil
+	}
+	return nil, fmt.Errorf("can not parse JSON-LD identifier: %#v", s)
+}
+
+func parseIdentifierString(a interface{}) (string, error) {
+	m, ok := a.(map[string]interface{})
+	if !ok {
+		return "", fmt.Errorf("unexpected type: %T", a)
+	}
+	id, ok := m["@id"].(string)
+	if !ok {
+		return "", fmt.Errorf("expected a @id key")
+	}
+	return id, nil
+}
+
+func parseLiteral(a interface{}) (quad.Value, error) {
+	switch a := a.(type) {
+	case string:
+		return quad.String(a), nil
+	case int64:
+		return quad.Int(a), nil
+	case float64:
+		return quad.Float(a), nil
+	case bool:
+		return quad.Bool(a), nil
+	case map[string]interface{}:
+		if val, ok := a["@value"].(string); ok {
+			if lang, ok := a["@language"].(string); ok {
+				return quad.LangString{Value: quad.String(val), Lang: lang}, nil
+			}
+			if typ, ok := a["@type"].(string); ok {
+				return quad.TypedString{Value: quad.String(val), Type: quad.IRI(typ)}, nil
+			}
+		}
+	}
+	return nil, fmt.Errorf("can not parse %#v as a literal", a)
+}
+
+func parseValue(a interface{}) (quad.Value, error) {
+	identifierString, err := parseIdentifierString(a)
+	if err == nil {
+		identifier, err := parseIdentifier(identifierString)
+		if err == nil {
+			return identifier, nil
+		}
+	}
+	lit, err := parseLiteral(a)
+	if err == nil {
+		return lit, nil
+	}
+	return nil, fmt.Errorf("can not parse JSON-LD value: %#v", a)
+}
diff --git a/query/linkedql/steps.go b/query/linkedql/steps.go
new file mode 100644
index 0000000..bb1fcb6
--- /dev/null
+++ b/query/linkedql/steps.go
@@ -0,0 +1,1390 @@
+package linkedql
+
+import (
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/graph/iterator"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/quad"
+)
+
+func init() {
+	Register(&Entity{})
+	Register(&Entities{})
+	Register(&Vertex{})
+	Register(&Placeholder{})
+	Register(&Visit{})
+	Register(&Out{})
+	Register(&As{})
+	Register(&Intersect{})
+	Register(&Is{})
+	Register(&Back{})
+	Register(&Both{})
+	Register(&Count{})
+	Register(&Difference{})
+	Register(&Filter{})
+	Register(&Follow{})
+	Register(&FollowReverse{})
+	Register(&Has{})
+	Register(&HasReverse{})
+	Register(&VisitReverse{})
+	Register(&In{})
+	Register(&ReversePropertyNames{})
+	Register(&Labels{})
+	Register(&Limit{})
+	Register(&PropertyNames{})
+	Register(&Properties{})
+	Register(&ReversePropertyNamesAs{})
+	Register(&PropertyNamesAs{})
+	Register(&ReverseProperties{})
+	Register(&Skip{})
+	Register(&Union{})
+	Register(&Unique{})
+	Register(&Order{})
+	Register(&Optional{})
+	Register(&Where{})
+	Register(&LessThan{})
+	Register(&LessThanEquals{})
+	Register(&GreaterThan{})
+	Register(&GreaterThanEquals{})
+}
+
+// Step is the tree representation of a call in a Path context.
+//
+// Example:
+// 		g.V(g.IRI("alice"))
+// 		is represented as
+// 		&Vertex{ Values: []quad.Value{quad.IRI("alice")} }
+//
+// 		g.V().out(g.IRI("likes"))
+// 		is represented as
+// 		&Out{ Properties: []quad.Value{quad.IRI("likes")}, From: &Vertex{} }
+type Step interface {
+	RegistryItem
+}
+
+// IteratorStep is a step that can build an Iterator.
+type IteratorStep interface {
+	Step
+	BuildIterator(qs graph.QuadStore) (query.Iterator, error)
+}
+
+// PathStep is a Step that can build a Path.
+type PathStep interface {
+	Step
+	BuildPath(qs graph.QuadStore) (*path.Path, error)
+}
+
+// EntityIdentifier is an interface to be used where a single entity identifier is expected.
+type EntityIdentifier interface {
+	BuildIdentifier() (quad.Value, error)
+}
+
+// EntityIRI is an entity IRI.
+type EntityIRI quad.IRI
+
+// BuildIdentifier implements EntityIdentifier
+func (i EntityIRI) BuildIdentifier() (quad.Value, error) {
+	return quad.IRI(i), nil
+}
+
+// EntityBNode is an entity BNode.
+type EntityBNode quad.BNode
+
+// BuildIdentifier implements EntityIdentifier
+func (i EntityBNode) BuildIdentifier() (quad.Value, error) {
+	return quad.BNode(i), nil
+}
+
+// EntityIdentifierString is an entity IRI or BNode strings.
+type EntityIdentifierString string
+
+// BuildIdentifier implements EntityIdentifier
+func (i EntityIdentifierString) BuildIdentifier() (quad.Value, error) {
+	return parseIdentifier(string(i))
+}
+
+var _ IteratorStep = (*Entity)(nil)
+var _ PathStep = (*Entity)(nil)
+
+// Entity corresponds to g.Entity().
+type Entity struct {
+	Identifier EntityIdentifier `json:"identifier"`
+}
+
+// Type implements Step.
+func (s *Entity) Type() quad.IRI {
+	return Prefix + "Entity"
+}
+
+// Description implements Step.
+func (s *Entity) Description() string {
+	return "resolves to the object matching given identifier in the graph."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Entity) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Entity) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	identifier, err := s.Identifier.BuildIdentifier()
+	if err != nil {
+		return nil, err
+	}
+	return path.StartPath(qs, identifier), nil
+}
+
+var _ IteratorStep = (*Entities)(nil)
+var _ PathStep = (*Entities)(nil)
+
+// Entities corresponds to g.Entities().
+type Entities struct {
+	Identifiers []EntityIdentifier `json:"identifiers"`
+}
+
+// Type implements Step.
+func (s *Entities) Type() quad.IRI {
+	return Prefix + "Entities"
+}
+
+// Description implements Step.
+func (s *Entities) Description() string {
+	return "resolves to all the existing objects in the graph. If provided with identifiers resolves to a sublist of all the existing identifiers in the graph."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Entities) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Entities) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	var values []quad.Value
+	for _, identifier := range s.Identifiers {
+		value, err := identifier.BuildIdentifier()
+		if err != nil {
+			return nil, err
+		}
+		values = append(values, value)
+	}
+	// TODO(iddan): Construct a path that only match entities
+	return path.StartPath(qs, values...), nil
+}
+
+var _ IteratorStep = (*Vertex)(nil)
+var _ PathStep = (*Vertex)(nil)
+
+// Vertex corresponds to g.Vertex() and g.V().
+type Vertex struct {
+	Values []quad.Value `json:"values"`
+}
+
+// Type implements Step.
+func (s *Vertex) Type() quad.IRI {
+	return Prefix + "Vertex"
+}
+
+// Description implements Step.
+func (s *Vertex) Description() string {
+	return "resolves to all the existing objects and primitive values in the graph. If provided with values resolves to a sublist of all the existing values in the graph."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Vertex) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Vertex) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	return path.StartPath(qs, s.Values...), nil
+}
+
+var _ PathStep = (*Placeholder)(nil)
+
+// Placeholder corresponds to .Placeholder().
+type Placeholder struct{}
+
+// Type implements Step.
+func (s *Placeholder) Type() quad.IRI {
+	return Prefix + "Placeholder"
+}
+
+// Description implements Step.
+func (s *Placeholder) Description() string {
+	return "is like Vertex but resolves to the values in the context it is placed in. It should only be used where a PathStep is expected and can't be resolved on its own."
+}
+
+// BuildPath implements PathStep.
+func (s *Placeholder) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	return path.StartMorphism(), nil
+}
+
+var _ IteratorStep = (*Visit)(nil)
+var _ PathStep = (*Visit)(nil)
+
+// Visit corresponds to .view().
+type Visit struct {
+	From       PathStep     `json:"from"`
+	Properties PropertyPath `json:"properties"`
+}
+
+// Type implements Step.
+func (s *Visit) Type() quad.IRI {
+	return Prefix + "Visit"
+}
+
+// Description implements Step.
+func (s *Visit) Description() string {
+	return "resolves to the values of the given property or properties in via of the current objects. If via is a path it's resolved values will be used as properties."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Visit) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Visit) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	viaPath, err := s.Properties.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Out(viaPath), nil
+}
+
+var _ IteratorStep = (*Out)(nil)
+var _ PathStep = (*Out)(nil)
+
+// Out is an alias for View.
+type Out struct {
+	Visit
+}
+
+// Type implements Step.
+func (s *Out) Type() quad.IRI {
+	return Prefix + "Out"
+}
+
+// Description implements Step.
+func (s *Out) Description() string {
+	return "aliases for View"
+}
+
+var _ IteratorStep = (*As)(nil)
+var _ PathStep = (*As)(nil)
+
+// As corresponds to .tag().
+type As struct {
+	From PathStep `json:"from"`
+	Name string   `json:"name"`
+}
+
+// Type implements Step.
+func (s *As) Type() quad.IRI {
+	return Prefix + "As"
+}
+
+// Description implements Step.
+func (s *As) Description() string {
+	return "assigns the resolved values of the from step to a given name. The name can be used with the Select and Documents steps to retrieve the values or to return to the values in further steps with the Back step. It resolves to the values of the from step."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *As) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *As) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Tag(s.Name), nil
+}
+
+var _ IteratorStep = (*Intersect)(nil)
+var _ PathStep = (*Intersect)(nil)
+
+// Intersect represents .intersect() and .and().
+type Intersect struct {
+	From  PathStep   `json:"from"`
+	Steps []PathStep `json:"steps"`
+}
+
+// Type implements Step.
+func (s *Intersect) Type() quad.IRI {
+	return Prefix + "Intersect"
+}
+
+// Description implements Step.
+func (s *Intersect) Description() string {
+	return "resolves to all the same values resolved by the from step and the provided steps."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Intersect) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Intersect) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath
+	for _, step := range s.Steps {
+		stepPath, err := step.BuildPath(qs)
+		if err != nil {
+			return nil, err
+		}
+		p = p.And(stepPath)
+	}
+	return p, nil
+}
+
+var _ IteratorStep = (*Is)(nil)
+var _ PathStep = (*Is)(nil)
+
+// Is corresponds to .back().
+type Is struct {
+	From   PathStep     `json:"from"`
+	Values []quad.Value `json:"values"`
+}
+
+// Type implements Step.
+func (s *Is) Type() quad.IRI {
+	return Prefix + "Is"
+}
+
+// Description implements Step.
+func (s *Is) Description() string {
+	return "resolves to all the values resolved by the from step which are included in provided values."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Is) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Is) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Is(s.Values...), nil
+}
+
+var _ IteratorStep = (*Back)(nil)
+var _ PathStep = (*Back)(nil)
+
+// Back corresponds to .back().
+type Back struct {
+	From PathStep `json:"from"`
+	Name string   `json:"name"`
+}
+
+// Type implements Step.
+func (s *Back) Type() quad.IRI {
+	return Prefix + "Back"
+}
+
+// Description implements Step.
+func (s *Back) Description() string {
+	return "resolves to the values of the previous the step or the values assigned to name in a former step."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Back) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Back) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Back(s.Name), nil
+}
+
+var _ IteratorStep = (*Both)(nil)
+var _ PathStep = (*Both)(nil)
+
+// Both corresponds to .both().
+type Both struct {
+	From       PathStep     `json:"from"`
+	Properties PropertyPath `json:"properties"`
+}
+
+// Type implements Step.
+func (s *Both) Type() quad.IRI {
+	return Prefix + "Both"
+}
+
+// Description implements Step.
+func (s *Both) Description() string {
+	return "is like View but resolves to both the object values and references to the values of the given properties in via. It is the equivalent for the Union of View and ViewReverse of the same property."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Both) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Both) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	viaPath, err := s.Properties.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Both(viaPath), nil
+}
+
+var _ IteratorStep = (*Count)(nil)
+var _ PathStep = (*Count)(nil)
+
+// Count corresponds to .count().
+type Count struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Count) Type() quad.IRI {
+	return Prefix + "Count"
+}
+
+// Description implements Step.
+func (s *Count) Description() string {
+	return "resolves to the number of the resolved values of the from step"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Count) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Count) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Count(), nil
+}
+
+var _ IteratorStep = (*Difference)(nil)
+var _ PathStep = (*Difference)(nil)
+
+// Difference corresponds to .difference().
+type Difference struct {
+	From  PathStep   `json:"from"`
+	Steps []PathStep `json:"steps"`
+}
+
+// Type implements Step.
+func (s *Difference) Type() quad.IRI {
+	return Prefix + "Difference"
+}
+
+// Description implements Step.
+func (s *Difference) Description() string {
+	return "resolves to all the values resolved by the from step different then the values resolved by the provided steps. Caution: it might be slow to execute."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Difference) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Difference) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	path := fromPath
+	for _, step := range s.Steps {
+		p, err := step.BuildPath(qs)
+		if err != nil {
+			return nil, err
+		}
+		path = path.Except(p)
+	}
+	return path, nil
+}
+
+var _ IteratorStep = (*Filter)(nil)
+var _ PathStep = (*Filter)(nil)
+
+// Filter corresponds to filter().
+type Filter struct {
+	From   PathStep `json:"from"`
+	Filter Operator `json:"filter"`
+}
+
+// Type implements Step.
+func (s *Filter) Type() quad.IRI {
+	return Prefix + "Filter"
+}
+
+// Description implements Step.
+func (s *Filter) Description() string {
+	return "applies constraints to a set of nodes. Can be used to filter values by range or match strings."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Filter) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Filter) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromIt, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return s.Filter.Apply(fromIt)
+}
+
+var _ IteratorStep = (*Follow)(nil)
+var _ PathStep = (*Follow)(nil)
+
+// Follow corresponds to .follow().
+type Follow struct {
+	From     PathStep `json:"from"`
+	Followed PathStep `json:"followed"`
+}
+
+// Type implements Step.
+func (s *Follow) Type() quad.IRI {
+	return Prefix + "Follow"
+}
+
+// Description implements Step.
+func (s *Follow) Description() string {
+	return "is the way to use a path prepared with Morphism. Applies the path chain on the morphism object to the current path. Starts as if at the g.M() and follows through the morphism path."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Follow) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Follow) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p, err := s.Followed.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Follow(p), nil
+}
+
+var _ IteratorStep = (*FollowReverse)(nil)
+var _ PathStep = (*FollowReverse)(nil)
+
+// FollowReverse corresponds to .followR().
+type FollowReverse struct {
+	From     PathStep `json:"from"`
+	Followed PathStep `json:"followed"`
+}
+
+// Type implements Step.
+func (s *FollowReverse) Type() quad.IRI {
+	return Prefix + "FollowReverse"
+}
+
+// Description implements Step.
+func (s *FollowReverse) Description() string {
+	return "is the same as follow but follows the chain in the reverse direction. Flips View and ViewReverse where appropriate, the net result being a virtual predicate followed in the reverse direction. Starts at the end of the morphism and follows it backwards (with appropriate flipped directions) to the g.M() location."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *FollowReverse) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *FollowReverse) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p, err := s.Followed.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.FollowReverse(p), nil
+}
+
+var _ IteratorStep = (*Has)(nil)
+var _ PathStep = (*Has)(nil)
+
+// Has corresponds to .has().
+type Has struct {
+	From     PathStep     `json:"from"`
+	Property PropertyPath `json:"property"`
+	Values   []quad.Value `json:"values"`
+}
+
+// Type implements Step.
+func (s *Has) Type() quad.IRI {
+	return Prefix + "Has"
+}
+
+// Description implements Step.
+func (s *Has) Description() string {
+	return "filters all paths which are, at this point, on the subject for the given predicate and object, but do not follow the path, merely filter the possible paths. Usually useful for starting with all nodes, or limiting to a subset depending on some predicate/value pair."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Has) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Has) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	viaPath, err := s.Property.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Has(viaPath, s.Values...), nil
+}
+
+var _ IteratorStep = (*HasReverse)(nil)
+var _ PathStep = (*HasReverse)(nil)
+
+// HasReverse corresponds to .hasR().
+type HasReverse struct {
+	From     PathStep     `json:"from"`
+	Property PropertyPath `json:"property"`
+	Values   []quad.Value `json:"values"`
+}
+
+// Type implements Step.
+func (s *HasReverse) Type() quad.IRI {
+	return Prefix + "HasReverse"
+}
+
+// Description implements Step.
+func (s *HasReverse) Description() string {
+	return "is the same as Has, but sets constraint in reverse direction."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *HasReverse) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *HasReverse) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	viaPath, err := s.Property.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.HasReverse(viaPath, s.Values...), nil
+}
+
+var _ IteratorStep = (*VisitReverse)(nil)
+var _ PathStep = (*VisitReverse)(nil)
+
+// VisitReverse corresponds to .viewReverse().
+type VisitReverse struct {
+	From       PathStep     `json:"from"`
+	Properties PropertyPath `json:"properties"`
+}
+
+// Type implements Step.
+func (s *VisitReverse) Type() quad.IRI {
+	return Prefix + "VisitReverse"
+}
+
+// Description implements Step.
+func (s *VisitReverse) Description() string {
+	return "is the inverse of View. Starting with the nodes in `path` on the object, follow the quads with predicates defined by `predicatePath` to their subjects."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *VisitReverse) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *VisitReverse) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	viaPath, err := s.Properties.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.In(viaPath), nil
+}
+
+var _ IteratorStep = (*In)(nil)
+var _ PathStep = (*In)(nil)
+
+// In is an alias for ViewReverse.
+type In struct {
+	VisitReverse
+}
+
+// Type implements Step.
+func (s *In) Type() quad.IRI {
+	return Prefix + "In"
+}
+
+// Description implements Step.
+func (s *In) Description() string {
+	return "aliases for ViewReverse"
+}
+
+var _ IteratorStep = (*ReversePropertyNames)(nil)
+var _ PathStep = (*ReversePropertyNames)(nil)
+
+// ReversePropertyNames corresponds to .reversePropertyNames().
+type ReversePropertyNames struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *ReversePropertyNames) Type() quad.IRI {
+	return Prefix + "ReversePropertyNames"
+}
+
+// Description implements Step.
+func (s *ReversePropertyNames) Description() string {
+	return "gets the list of predicates that are pointing in to a node."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *ReversePropertyNames) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *ReversePropertyNames) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.InPredicates(), nil
+}
+
+var _ IteratorStep = (*Labels)(nil)
+var _ PathStep = (*Labels)(nil)
+
+// Labels corresponds to .labels().
+type Labels struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Labels) Type() quad.IRI {
+	return Prefix + "Labels"
+}
+
+// Description implements Step.
+func (s *Labels) Description() string {
+	return "gets the list of inbound and outbound quad labels"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Labels) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Labels) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Labels(), nil
+}
+
+var _ IteratorStep = (*Limit)(nil)
+var _ PathStep = (*Limit)(nil)
+
+// Limit corresponds to .limit().
+type Limit struct {
+	From  PathStep `json:"from"`
+	Limit int64    `json:"limit"`
+}
+
+// Type implements Step.
+func (s *Limit) Type() quad.IRI {
+	return Prefix + "Limit"
+}
+
+// Description implements Step.
+func (s *Limit) Description() string {
+	return "limits a number of nodes for current path."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Limit) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Limit) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Limit(s.Limit), nil
+}
+
+var _ IteratorStep = (*PropertyNames)(nil)
+var _ PathStep = (*PropertyNames)(nil)
+
+// PropertyNames corresponds to .propertyNames().
+type PropertyNames struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *PropertyNames) Type() quad.IRI {
+	return Prefix + "PropertyNames"
+}
+
+// Description implements Step.
+func (s *PropertyNames) Description() string {
+	return "gets the list of predicates that are pointing out from a node."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *PropertyNames) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *PropertyNames) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.OutPredicates(), nil
+}
+
+var _ IteratorStep = (*Properties)(nil)
+var _ PathStep = (*Properties)(nil)
+
+// Properties corresponds to .properties().
+type Properties struct {
+	From PathStep `json:"from"`
+	// TODO(iddan): Use PropertyPath
+	Names []quad.IRI `json:"names"`
+}
+
+// Type implements Step.
+func (s *Properties) Type() quad.IRI {
+	return Prefix + "Properties"
+}
+
+// Description implements Step.
+func (s *Properties) Description() string {
+	return "adds tags for all properties of the current entity"
+}
+
+// BuildIterator implements IteratorStep.
+// TODO(iddan): Default tag to Via.
+func (s *Properties) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Properties) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath
+	if s.Names != nil {
+		for _, name := range s.Names {
+			tag := string(name)
+			p = p.Save(name, tag)
+		}
+	} else {
+		panic("Not implemented: should tag all properties")
+	}
+	return p, nil
+}
+
+var _ IteratorStep = (*ReversePropertyNamesAs)(nil)
+var _ PathStep = (*ReversePropertyNamesAs)(nil)
+
+// ReversePropertyNamesAs corresponds to .reversePropertyNamesAs().
+type ReversePropertyNamesAs struct {
+	From PathStep `json:"from"`
+	Tag  string   `json:"tag"`
+}
+
+// Type implements Step.
+func (s *ReversePropertyNamesAs) Type() quad.IRI {
+	return Prefix + "ReversePropertyNamesAs"
+}
+
+// Description implements Step.
+func (s *ReversePropertyNamesAs) Description() string {
+	return "tags the list of predicates that are pointing in to a node."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *ReversePropertyNamesAs) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *ReversePropertyNamesAs) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.SavePredicates(true, s.Tag), nil
+}
+
+var _ IteratorStep = (*PropertyNamesAs)(nil)
+var _ PathStep = (*PropertyNamesAs)(nil)
+
+// PropertyNamesAs corresponds to .propertyNamesAs().
+type PropertyNamesAs struct {
+	From PathStep `json:"from"`
+	Tag  string   `json:"tag"`
+}
+
+// Type implements Step.
+func (s *PropertyNamesAs) Type() quad.IRI {
+	return Prefix + "PropertyNamesAs"
+}
+
+// Description implements Step.
+func (s *PropertyNamesAs) Description() string {
+	return "tags the list of predicates that are pointing out from a node."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *PropertyNamesAs) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *PropertyNamesAs) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.SavePredicates(false, s.Tag), nil
+}
+
+var _ IteratorStep = (*ReverseProperties)(nil)
+var _ PathStep = (*ReverseProperties)(nil)
+
+// ReverseProperties corresponds to .reverseProperties().
+type ReverseProperties struct {
+	From PathStep `json:"from"`
+	// TODO(iddan): Use property path
+	Names []quad.IRI `json:"names"`
+}
+
+// Type implements Step.
+func (s *ReverseProperties) Type() quad.IRI {
+	return Prefix + "ReverseProperties"
+}
+
+// Description implements Step.
+func (s *ReverseProperties) Description() string {
+	return "gets all the properties the current entity / value is referenced at"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *ReverseProperties) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *ReverseProperties) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath
+	for _, name := range s.Names {
+		p = fromPath.SaveReverse(name, string(name))
+	}
+	return p, nil
+}
+
+var _ IteratorStep = (*Skip)(nil)
+var _ PathStep = (*Skip)(nil)
+
+// Skip corresponds to .skip().
+type Skip struct {
+	From   PathStep `json:"from"`
+	Offset int64    `json:"offset"`
+}
+
+// Type implements Step.
+func (s *Skip) Type() quad.IRI {
+	return Prefix + "Skip"
+}
+
+// Description implements Step.
+func (s *Skip) Description() string {
+	return "skips a number of nodes for current path."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Skip) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Skip) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Skip(s.Offset), nil
+}
+
+var _ IteratorStep = (*Union)(nil)
+var _ PathStep = (*Union)(nil)
+
+// Union corresponds to .union() and .or().
+type Union struct {
+	From  PathStep   `json:"from"`
+	Steps []PathStep `json:"steps"`
+}
+
+// Type implements Step.
+func (s *Union) Type() quad.IRI {
+	return Prefix + "Union"
+}
+
+// Description implements Step.
+func (s *Union) Description() string {
+	return "returns the combined paths of the two queries. Notice that it's per-path, not per-node. Once again, if multiple paths reach the same destination, they might have had different ways of getting there (and different tags)."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Union) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Union) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath
+	for _, step := range s.Steps {
+		valuePath, err := step.BuildPath(qs)
+		if err != nil {
+			return nil, err
+		}
+		p = p.Or(valuePath)
+	}
+	return p, nil
+}
+
+var _ IteratorStep = (*Unique)(nil)
+var _ PathStep = (*Unique)(nil)
+
+// Unique corresponds to .unique().
+type Unique struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Unique) Type() quad.IRI {
+	return Prefix + "Unique"
+}
+
+// Description implements Step.
+func (s *Unique) Description() string {
+	return "removes duplicate values from the path."
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Unique) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Unique) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Unique(), nil
+}
+
+var _ IteratorStep = (*Order)(nil)
+var _ PathStep = (*Order)(nil)
+
+// Order corresponds to .order().
+type Order struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Order) Type() quad.IRI {
+	return Prefix + "Order"
+}
+
+// Description implements Step.
+func (s *Order) Description() string {
+	return "sorts the results in ascending order according to the current entity / value"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Order) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Order) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Order(), nil
+}
+
+var _ IteratorStep = (*Optional)(nil)
+var _ PathStep = (*Optional)(nil)
+
+// Optional corresponds to .optional().
+type Optional struct {
+	From PathStep `json:"from"`
+	Step PathStep `json:"step"`
+}
+
+// Type implements Step.
+func (s *Optional) Type() quad.IRI {
+	return Prefix + "Optional"
+}
+
+// Description implements Step.
+func (s *Optional) Description() string {
+	return "attempts to follow the given path from the current entity / value, if fails the entity / value will still be kept in the results"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Optional) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Optional) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p, err := s.Step.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Optional(p), nil
+}
+
+var _ IteratorStep = (*Where)(nil)
+var _ PathStep = (*Where)(nil)
+
+// Where corresponds to .where().
+type Where struct {
+	From  PathStep   `json:"from"`
+	Steps []PathStep `json:"steps"`
+}
+
+// Type implements Step.
+func (s *Where) Type() quad.IRI {
+	return Prefix + "Where"
+}
+
+// Description implements Step.
+func (s *Where) Description() string {
+	return "applies each provided step in steps in isolation on from"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *Where) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements PathStep.
+func (s *Where) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	p := fromPath
+	for _, step := range s.Steps {
+		stepPath, err := step.BuildPath(qs)
+		if err != nil {
+			return nil, err
+		}
+		p = p.And(stepPath.Reverse())
+	}
+	return p, nil
+}
+
+var _ IteratorStep = (*LessThan)(nil)
+var _ PathStep = (*LessThan)(nil)
+
+// LessThan corresponds to lt().
+type LessThan struct {
+	From  PathStep   `json:"from"`
+	Value quad.Value `json:"value"`
+}
+
+// Type implements Step.
+func (s *LessThan) Type() quad.IRI {
+	return Prefix + "LessThan"
+}
+
+// Description implements Step.
+func (s *LessThan) Description() string {
+	return "Less than filters out values that are not less than given value"
+}
+
+// BuildIterator implements IteratorStep.
+func (s *LessThan) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements Step.
+func (s *LessThan) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Filter(iterator.CompareLT, s.Value), nil
+}
+
+var _ IteratorStep = (*LessThanEquals)(nil)
+var _ PathStep = (*LessThanEquals)(nil)
+
+// LessThanEquals corresponds to lte().
+type LessThanEquals struct {
+	From  PathStep   `json:"from"`
+	Value quad.Value `json:"value"`
+}
+
+// Type implements Step.
+func (s *LessThanEquals) Type() quad.IRI {
+	return Prefix + "LessThanEquals"
+}
+
+// Description implements Step.
+func (s *LessThanEquals) Description() string {
+	return "Less than equals filters out values that are not less than or equal given value"
+}
+
+// BuildIterator implements Step.
+func (s *LessThanEquals) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements Step.
+func (s *LessThanEquals) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Filter(iterator.CompareLTE, s.Value), nil
+}
+
+var _ IteratorStep = (*GreaterThan)(nil)
+var _ PathStep = (*GreaterThan)(nil)
+
+// GreaterThan corresponds to gt().
+type GreaterThan struct {
+	From  PathStep   `json:"from"`
+	Value quad.Value `json:"value"`
+}
+
+// Type implements Step.
+func (s *GreaterThan) Type() quad.IRI {
+	return Prefix + "GreaterThan"
+}
+
+// Description implements Step.
+func (s *GreaterThan) Description() string {
+	return "Greater than equals filters out values that are not greater than given value"
+}
+
+// BuildIterator implements Step.
+func (s *GreaterThan) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements Step.
+func (s *GreaterThan) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Filter(iterator.CompareGT, s.Value), nil
+}
+
+var _ IteratorStep = (*GreaterThanEquals)(nil)
+var _ PathStep = (*GreaterThanEquals)(nil)
+
+// GreaterThanEquals corresponds to gte().
+type GreaterThanEquals struct {
+	From  PathStep   `json:"from"`
+	Value quad.Value `json:"value"`
+}
+
+// Type implements Step.
+func (s *GreaterThanEquals) Type() quad.IRI {
+	return Prefix + "GreaterThanEquals"
+}
+
+// Description implements Step.
+func (s *GreaterThanEquals) Description() string {
+	return "Greater than equals filters out values that are not greater than or equal given value"
+}
+
+// BuildIterator implements Step.
+func (s *GreaterThanEquals) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return NewValueIteratorFromPathStep(s, qs)
+}
+
+// BuildPath implements Step.
+func (s *GreaterThanEquals) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	fromPath, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return fromPath.Filter(iterator.CompareGTE, s.Value), nil
+}
diff --git a/query/linkedql/steps_final.go b/query/linkedql/steps_final.go
new file mode 100644
index 0000000..de451c7
--- /dev/null
+++ b/query/linkedql/steps_final.go
@@ -0,0 +1,128 @@
+package linkedql
+
+import (
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/quad"
+)
+
+func init() {
+	Register(&Select{})
+	Register(&SelectFirst{})
+	Register(&Value{})
+	Register(&Documents{})
+}
+
+var _ IteratorStep = (*Select)(nil)
+
+// Select corresponds to .select().
+type Select struct {
+	Tags []string `json:"tags"`
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Select) Type() quad.IRI {
+	return Prefix + "Select"
+}
+
+// Description implements Step.
+func (s *Select) Description() string {
+	return "Select returns flat records of tags matched in the query"
+}
+
+// BuildIterator implements IteratorStep
+func (s *Select) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	valueIt, err := NewValueIteratorFromPathStep(s.From, qs)
+	if err != nil {
+		return nil, err
+	}
+	return &TagsIterator{valueIt: valueIt, selected: s.Tags}, nil
+}
+
+var _ IteratorStep = (*SelectFirst)(nil)
+
+// SelectFirst corresponds to .selectFirst().
+type SelectFirst struct {
+	Tags []string `json:"tags"`
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *SelectFirst) Type() quad.IRI {
+	return Prefix + "SelectFirst"
+}
+
+// Description implements Step.
+func (s *SelectFirst) Description() string {
+	return "Like Select but only returns the first result"
+}
+
+func singleValueIteratorFromPathStep(step PathStep, qs graph.QuadStore) (*ValueIterator, error) {
+	p, err := step.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	return NewValueIterator(p.Limit(1), qs), nil
+}
+
+// BuildIterator implements IteratorStep
+func (s *SelectFirst) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	it, err := singleValueIteratorFromPathStep(s.From, qs)
+	if err != nil {
+		return nil, err
+	}
+	return &TagsIterator{it, s.Tags}, nil
+}
+
+var _ IteratorStep = (*Value)(nil)
+
+// Value corresponds to .value().
+type Value struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Value) Type() quad.IRI {
+	return Prefix + "Value"
+}
+
+// Description implements Step.
+func (s *Value) Description() string {
+	return "Value returns a single value matched in the query"
+}
+
+// BuildIterator implements IteratorStep
+func (s *Value) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	return singleValueIteratorFromPathStep(s.From, qs)
+}
+
+var _ IteratorStep = (*Documents)(nil)
+
+// Documents corresponds to .documents().
+type Documents struct {
+	From PathStep `json:"from"`
+}
+
+// Type implements Step.
+func (s *Documents) Type() quad.IRI {
+	return Prefix + "Documents"
+}
+
+// Description implements Step.
+func (s *Documents) Description() string {
+	return "Documents return documents of the tags matched in the query associated with their entity"
+}
+
+// BuildIterator implements IteratorStep
+func (s *Documents) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	p, err := s.From.BuildPath(qs)
+	if err != nil {
+		return nil, err
+	}
+	it, err := NewValueIterator(p, qs), nil
+	if err != nil {
+		return nil, err
+	}
+	return NewDocumentIterator(it), nil
+}
diff --git a/server/http/api_v2.go b/server/http/api_v2.go
index a53919b..f67893e 100644
--- a/server/http/api_v2.go
+++ b/server/http/api_v2.go
@@ -192,6 +192,20 @@ func (api *APIv2) handleForRequest(r *http.Request) (*graph.Handle, error) {
 	return HandleForRequest(api.h, api.wtyp, api.wopt, r)
 }
 
+// writeResponse represents the response received for a successful write
+type writeResponse struct {
+	Result string `json:"result"`
+	Count  int    `json:"count"`
+}
+
+// newWriteResponse creates a new WriteResponse for given count of quads written
+func newWriteResponse(count int) writeResponse {
+	return writeResponse{
+		Result: fmt.Sprintf("Successfully wrote %d quads.", count),
+		Count:  count,
+	}
+}
+
 func (api *APIv2) ServeWrite(w http.ResponseWriter, r *http.Request) {
 	defer r.Body.Close()
 	if api.ro {
@@ -229,7 +243,9 @@ func (api *APIv2) ServeWrite(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	w.Header().Set(hdrContentType, contentTypeJSON)
-	fmt.Fprintf(w, `{"result": "Successfully wrote %d quads.", "count": %d}`+"\n", n, n)
+	response := newWriteResponse(n)
+	encoder := json.NewEncoder(w)
+	encoder.Encode(response)
 }
 
 func (api *APIv2) ServeDelete(w http.ResponseWriter, r *http.Request) {


Test patch:
diff --git a/inference/inference_test.go b/inference/inference_test.go
new file mode 100644
index 0000000..6716e16
--- /dev/null
+++ b/inference/inference_test.go
@@ -0,0 +1,596 @@
+package inference
+
+import (
+	"testing"
+
+	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/voc/rdf"
+	"github.com/cayleygraph/quad/voc/rdfs"
+	"github.com/stretchr/testify/require"
+)
+
+func triple(subject quad.Value, predicate quad.IRI, object quad.Value) quad.Quad {
+	return quad.Quad{Subject: subject, Predicate: predicate, Object: object}
+}
+
+var (
+	domain           = quad.IRI(rdfs.Domain)
+	prange           = quad.IRI(rdfs.Range)
+	ptype            = quad.IRI(rdf.Type)
+	class            = quad.IRI(rdfs.Class)
+	literal          = quad.IRI(rdfs.Literal)
+	property         = quad.IRI(rdf.Property)
+	subClassOf       = quad.IRI(rdfs.SubClassOf)
+	subPropertyOf    = quad.IRI(rdfs.SubPropertyOf)
+	alice            = quad.IRI("alice")
+	aliceName        = quad.String("Alice")
+	bob              = quad.IRI("bob")
+	engineer         = quad.IRI("Engineer")
+	information      = quad.IRI("information")
+	likes            = quad.IRI("likes")
+	name             = quad.IRI("name")
+	person           = quad.IRI("Person")
+	personal         = quad.IRI("personal")
+	softwareEngineer = quad.IRI("SoftwareEngineer")
+)
+var (
+	aliceIsPerson                    = triple(alice, ptype, person)
+	aliceLikesBob                    = triple(alice, likes, bob)
+	likesRangePerson                 = triple(likes, prange, person)
+	engineerClass                    = triple(engineer, ptype, class)
+	engineerSubClass                 = triple(engineer, subClassOf, person)
+	nameDomainPerson                 = triple(name, domain, person)
+	nameProperty                     = triple(name, ptype, property)
+	nameSubPropertyOfPersonal        = triple(name, subPropertyOf, personal)
+	personalProperty                 = triple(personal, ptype, property)
+	personalSubPropertyOfInformation = triple(personal, subPropertyOf, information)
+	personClass                      = triple(person, ptype, class)
+	softwareEngineerClass            = triple(softwareEngineer, ptype, class)
+	aliceNameAlice                   = triple(alice, name, aliceName)
+)
+var (
+	engineerAndSoftwareEngineerSubClasses = []quad.Quad{
+		engineerSubClass,
+		triple(softwareEngineer, subClassOf, engineer),
+	}
+	engineerAndPersonClasses = []quad.Quad{
+		engineerClass,
+		personClass,
+	}
+)
+
+func TestClassName(t *testing.T) {
+	iri := alice
+	c := Class{name: iri}
+	require.Equal(t, c.Name(), iri, "Name was not set correctly for the class")
+}
+
+func TestPropertyName(t *testing.T) {
+	iri := likes
+	p := Property{name: iri}
+	require.Equal(t, p.Name(), iri, "Name was not set correctly for the property")
+}
+
+func TestReferencedType(t *testing.T) {
+	store := NewStore()
+	q := aliceIsPerson
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(person)
+	require.NotNil(t, createdClass, "Class was not created")
+}
+
+func TestReferencedBNodeType(t *testing.T) {
+	store := NewStore()
+	name := quad.BNode("123")
+	q := triple(alice, ptype, name)
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(name)
+	require.NotNil(t, createdClass, "Class was not created")
+}
+
+func TestReferencedProperty(t *testing.T) {
+	store := NewStore()
+	q := aliceLikesBob
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(likes)
+	require.NotNil(t, createdProperty, "Property was not created")
+}
+
+func TestNewClass(t *testing.T) {
+	store := NewStore()
+	q := personClass
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(person)
+	require.NotNil(t, createdClass, "Class was not created")
+}
+
+func TestNewBNodeClass(t *testing.T) {
+	store := NewStore()
+	name := quad.BNode("123")
+	q := triple(name, ptype, class)
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(name)
+	require.NotNil(t, createdClass, "Class was not created")
+}
+
+func TestInvalidNewClass(t *testing.T) {
+	store := NewStore()
+	name := quad.String("Foo")
+	q := triple(alice, ptype, name)
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(name)
+	require.Nil(t, createdClass, "Invalid class was created")
+}
+
+func TestNewProperty(t *testing.T) {
+	store := NewStore()
+	q := nameProperty
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(name)
+	require.NotNil(t, createdProperty, "Property was not created")
+}
+
+func TestInvalidNewProperty(t *testing.T) {
+	store := NewStore()
+	name := quad.String("Foo")
+	q := quad.Quad{Subject: alice, Predicate: name, Object: bob}
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(name)
+	require.Nil(t, createdProperty, "Invalid property was created")
+}
+
+func TestSubClass(t *testing.T) {
+	store := NewStore()
+	q := engineerSubClass
+	store.ProcessQuads(q)
+	createdClass := store.GetClass(engineer)
+	createdSuperClass := store.GetClass(person)
+	require.NotNil(t, createdClass, "Class was not created")
+	require.NotNil(t, createdSuperClass, "Super class was not created")
+	if _, ok := createdClass.super[createdSuperClass]; !ok {
+		t.Error("Super class was not registered for class")
+	}
+	if _, ok := createdSuperClass.sub[createdClass]; !ok {
+		t.Error("Class was not registered for super class")
+	}
+}
+
+func TestSubProperty(t *testing.T) {
+	store := NewStore()
+	q := nameSubPropertyOfPersonal
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(name)
+	createdSuperProperty := store.GetProperty(personal)
+	require.NotNil(t, createdProperty, "Property was not created")
+	require.NotNil(t, createdSuperProperty, "Super property was not created")
+	if _, ok := createdProperty.super[createdSuperProperty]; !ok {
+		t.Error("Super property was not registered for property")
+	}
+	if _, ok := createdSuperProperty.sub[createdProperty]; !ok {
+		t.Error("Property was not registered for super property")
+	}
+}
+
+func TestPropertyDomain(t *testing.T) {
+	store := NewStore()
+	q := nameDomainPerson
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(name)
+	createdClass := store.GetClass(person)
+	require.NotNil(t, createdProperty, "Property was not created")
+	require.NotNil(t, createdClass, "Domain class was not created")
+	if createdProperty.Domain() != createdClass {
+		t.Error("Domain class was not registered for property")
+	}
+	if _, ok := createdClass.ownProp[createdProperty]; !ok {
+		t.Error("Property was not registered for class")
+	}
+}
+
+func TestPropertyRange(t *testing.T) {
+	store := NewStore()
+	q := likesRangePerson
+	store.ProcessQuads(q)
+	createdProperty := store.GetProperty(likes)
+	createdClass := store.GetClass(person)
+	require.NotNil(t, createdProperty, "Property was not created")
+	require.NotNil(t, createdClass, "Range class was not created")
+	if createdProperty.Range() != createdClass {
+		t.Error("Range class was not registered for property")
+	}
+	if _, ok := createdClass.inProp[createdProperty]; !ok {
+		t.Error("Property was not registered for class")
+	}
+}
+
+func TestIsSubClassOf(t *testing.T) {
+	store := NewStore()
+	q := engineerSubClass
+	store.ProcessQuads(q)
+	if !store.GetClass(engineer).IsSubClassOf(store.GetClass(person)) {
+		t.Error("Class was not registered as subclass of super class")
+	}
+}
+
+func TestIsSubClassOfRecursive(t *testing.T) {
+	store := NewStore()
+	quads := engineerAndSoftwareEngineerSubClasses
+	store.ProcessQuads(quads...)
+	if !store.GetClass(softwareEngineer).IsSubClassOf(store.GetClass(person)) {
+		t.Error("Class was not registered as subclass of super class")
+	}
+}
+
+func TestIsSubClassOfItself(t *testing.T) {
+	store := NewStore()
+	q := personClass
+	store.ProcessQuads(q)
+	if !store.GetClass(person).IsSubClassOf(store.GetClass(person)) {
+		t.Error("IsSubClassOf itself doesn't work")
+	}
+}
+
+func TestIsSubClassOfResource(t *testing.T) {
+	store := NewStore()
+	q := personClass
+	store.ProcessQuads(q)
+	if !store.GetClass(person).IsSubClassOf(store.GetClass(quad.IRI(rdfs.Resource))) {
+		t.Error("ItSubClassOf rdfs:Resource doesn't work")
+	}
+}
+
+func TestIsSubPropertyOf(t *testing.T) {
+	store := NewStore()
+	q := nameSubPropertyOfPersonal
+	store.ProcessQuads(q)
+	if !store.GetProperty(name).IsSubPropertyOf(store.GetProperty(personal)) {
+		t.Error("Property was not registered as subproperty of super property")
+	}
+}
+
+func TestIsSubPropertyOfRecursive(t *testing.T) {
+	store := NewStore()
+	quads := []quad.Quad{
+		nameSubPropertyOfPersonal,
+		personalSubPropertyOfInformation,
+	}
+	store.ProcessQuads(quads...)
+	if !store.GetProperty(name).IsSubPropertyOf(store.GetProperty(information)) {
+		t.Error("Property was not registered as subproperty of super property")
+	}
+}
+
+func TestIsSubPropertyOfItself(t *testing.T) {
+	store := NewStore()
+	q := nameProperty
+	store.ProcessQuads(q)
+	if !store.GetProperty(name).IsSubPropertyOf(store.GetProperty(name)) {
+		t.Error("IsSubPropertyOf itself doesn't work")
+	}
+}
+
+func TestUnprocessInvalidQuad(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(quad.Quad{Subject: alice, Predicate: quad.String("Foo"), Object: person})
+}
+
+func TestUnprocessInvalidTypeQuad(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(quad.Quad{Subject: alice, Predicate: ptype, Object: quad.String("Foo")})
+}
+
+func TestDeleteReferencedType(t *testing.T) {
+	store := NewStore()
+	q := aliceIsPerson
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdClass := store.GetClass(person)
+	require.Nil(t, createdClass, "Class was not deleted")
+}
+
+func TestDeleteClassWithSubClass(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		engineerClass,
+		engineerSubClass,
+	)
+	q := personClass
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	subClass := store.GetClass(engineer)
+	if len(subClass.super) != 0 {
+		t.Error("Class was not unreferenced")
+	}
+}
+
+func TestDeleteClassWithSuperClass(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		personClass,
+		engineerSubClass,
+	)
+	q := engineerClass
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	superClass := store.GetClass(person)
+	if len(superClass.sub) != 0 {
+		t.Error("Class was not unreferenced")
+	}
+}
+
+func TestDeleteNewClass(t *testing.T) {
+	store := NewStore()
+	q := personClass
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdClass := store.GetClass(person)
+	require.Nil(t, createdClass, "Class was not deleted")
+}
+
+func TestDeleteNewProperty(t *testing.T) {
+	store := NewStore()
+	q := nameProperty
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdProperty := store.GetProperty(name)
+	require.Nil(t, createdProperty, "Property was not deleted")
+}
+
+func TestDeletePropertyWithSubProperty(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		nameSubPropertyOfPersonal,
+	)
+	q := personalProperty
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	subProperty := store.GetProperty(name)
+	if len(subProperty.super) != 0 {
+		t.Error("Property was not unreferenced")
+	}
+}
+
+func TestDeletePropertyWithSuperProperty(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		personalProperty,
+		nameSubPropertyOfPersonal,
+	)
+	q := nameProperty
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	superProperty := store.GetProperty(personal)
+	if len(superProperty.sub) != 0 {
+		t.Error("Property was not unreferenced")
+	}
+}
+
+func TestDeleteSubClass(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(engineerAndPersonClasses...)
+	q := engineerSubClass
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdClass := store.GetClass(engineer)
+	createdSuperClass := store.GetClass(person)
+	// TODO(iddan): what about garbage collection?
+	if _, ok := createdClass.super[createdSuperClass]; ok {
+		t.Error("Super class was not unregistered for class")
+	}
+	if _, ok := createdSuperClass.sub[createdClass]; ok {
+		t.Error("Class was not unregistered for super class")
+	}
+}
+
+func TestDeleteSubProperty(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		personalProperty,
+	)
+	q := nameSubPropertyOfPersonal
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdProperty := store.GetProperty(name)
+	createdSuperProperty := store.GetProperty(personal)
+	// TODO(iddan): what about garbage collection?
+	if _, ok := createdProperty.super[createdSuperProperty]; ok {
+		t.Error("Super property was not unregistered for property")
+	}
+	if _, ok := createdSuperProperty.sub[createdProperty]; ok {
+		t.Error("Property was not unregistered for super property")
+	}
+}
+
+func TestDeletePropertyDomain(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		personClass,
+	)
+	q := nameDomainPerson
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdProperty := store.GetProperty(name)
+	createdClass := store.GetClass(person)
+	// TODO(iddan): what about garbage collection?
+	if createdProperty.Domain() == createdClass {
+		t.Error("Domain class was not unregistered for property")
+	}
+	if _, ok := createdClass.ownProp[createdProperty]; ok {
+		t.Error("Property was not unregistered for class")
+	}
+}
+
+func TestDeletePropertyRange(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		quad.Quad{Subject: literal, Predicate: ptype, Object: class},
+	)
+	q := quad.Quad{Subject: name, Predicate: prange, Object: literal}
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	createdProperty := store.GetProperty(name)
+	createdClass := store.GetClass(literal)
+	// TODO(iddan): what about garbage collection?
+	if createdProperty.Range() == createdClass {
+		t.Error("Range class was not unregistered for property")
+	}
+	if _, ok := createdClass.inProp[createdProperty]; ok {
+		t.Error("Property was not unregistered for class")
+	}
+}
+
+func TestDeleteIsSubClassOf(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(engineerAndPersonClasses...)
+	q := engineerSubClass
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	if store.GetClass(engineer).IsSubClassOf(store.GetClass(person)) {
+		t.Error("Class was not unregistered as subclass of super class")
+	}
+}
+
+func TestDeleteIsSubClassOfRecursive(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		engineerClass,
+		personClass,
+		softwareEngineerClass,
+	)
+	quads := engineerAndSoftwareEngineerSubClasses
+	store.ProcessQuads(quads...)
+	store.UnprocessQuads(quads...)
+	if store.GetClass(softwareEngineer).IsSubClassOf(store.GetClass(person)) {
+		t.Error("Class was not unregistered as subclass of super class")
+	}
+}
+
+func TestDeleteIsSubPropertyOf(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		personalProperty,
+	)
+	q := nameSubPropertyOfPersonal
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	if store.GetProperty(name).IsSubPropertyOf(store.GetProperty(personal)) {
+		t.Error("Property was not unregistered as subproperty of super property")
+	}
+}
+
+func TestDeleteIsSubPropertyOfRecursive(t *testing.T) {
+	store := NewStore()
+	store.ProcessQuads(
+		nameProperty,
+		personalProperty,
+		quad.Quad{Subject: information, Predicate: ptype, Object: property},
+	)
+	quads := []quad.Quad{
+		nameSubPropertyOfPersonal,
+		personalSubPropertyOfInformation,
+	}
+	store.ProcessQuads(quads...)
+	store.UnprocessQuads(quads...)
+	if store.GetProperty(name).IsSubPropertyOf(store.GetProperty(information)) {
+		t.Error("Property was not unregistered as subproperty of super property")
+	}
+}
+
+func TestClassIsReference(t *testing.T) {
+	store := NewStore()
+	q := aliceIsPerson
+	store.ProcessQuads(q)
+	class := store.GetClass(person)
+	if !class.isReferenced() {
+		t.Error("Class should be referenced")
+	}
+}
+
+func TestPropertyIsReference(t *testing.T) {
+	store := NewStore()
+	q := aliceLikesBob
+	store.ProcessQuads(q)
+	property := store.GetProperty(likes)
+	if !property.isReferenced() {
+		t.Error("Property should be referenced")
+	}
+}
+
+func TestClassUnreference(t *testing.T) {
+	store := NewStore()
+	q := aliceIsPerson
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	require.Nil(t, store.GetClass(person), "class was not garbage collected")
+}
+
+func TestPropertyUnreference(t *testing.T) {
+	store := NewStore()
+	q := aliceLikesBob
+	store.ProcessQuads(q)
+	store.UnprocessQuads(q)
+	require.Nil(t, store.GetProperty(likes), "property was not garbage collected")
+}
+
+func TestDomainClassInstance(t *testing.T) {
+	store := NewStore()
+	quads := []quad.Quad{
+		nameDomainPerson,
+		aliceNameAlice,
+	}
+	store.ProcessQuads(quads...)
+	class := store.GetClass(person)
+	require.NotNil(t, class)
+	require.True(t, class.isReferenced())
+	require.Equal(t, 1, class.references)
+	store.UnprocessQuads(aliceNameAlice)
+	require.True(t, class.isReferenced())
+	require.Equal(t, 0, class.references)
+	store.UnprocessQuads(nameDomainPerson)
+	require.False(t, class.isReferenced())
+	require.Equal(t, 0, class.references)
+}
+
+func TestRangeClassInstance(t *testing.T) {
+	store := NewStore()
+	quads := []quad.Quad{
+		likesRangePerson,
+		aliceLikesBob,
+	}
+	store.ProcessQuads(quads...)
+	class := store.GetClass(person)
+	require.NotNil(t, class)
+	require.Equal(t, 1, class.references)
+	require.True(t, class.isReferenced())
+	store.UnprocessQuads(aliceLikesBob)
+	require.Equal(t, 0, class.references)
+	require.True(t, class.isReferenced())
+	store.UnprocessQuads(likesRangePerson)
+	require.Equal(t, 0, class.references)
+	require.False(t, class.isReferenced())
+}
+
+func TestDeleteNonExistingClass(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(personClass)
+}
+
+func TestDeleteNonExistingProperty(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(personalProperty)
+}
+
+func TestDeleteNonExistingClassInstance(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(aliceIsPerson)
+}
+
+func TestDeleteNonExistingUsedProperty(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(aliceNameAlice)
+}
diff --git a/internal/linkedql/schema/schema_test.go b/internal/linkedql/schema/schema_test.go
new file mode 100644
index 0000000..ada1f61
--- /dev/null
+++ b/internal/linkedql/schema/schema_test.go
@@ -0,0 +1,15 @@
+package schema
+
+import (
+	"encoding/json"
+	"testing"
+)
+
+func TestMarshalSchema(t *testing.T) {
+	out := Generate()
+	var o interface{}
+	err := json.Unmarshal(out, &o)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/query/linkedql/registry_test.go b/query/linkedql/registry_test.go
new file mode 100644
index 0000000..d146546
--- /dev/null
+++ b/query/linkedql/registry_test.go
@@ -0,0 +1,124 @@
+package linkedql
+
+import (
+	"testing"
+
+	"github.com/cayleygraph/cayley/graph"
+	"github.com/cayleygraph/cayley/query"
+	"github.com/cayleygraph/cayley/query/path"
+	"github.com/cayleygraph/quad"
+
+	"github.com/stretchr/testify/require"
+)
+
+func init() {
+	Register(&TestStep{})
+}
+
+var unmarshalCases = []struct {
+	name string
+	data string
+	exp  Step
+}{
+	{
+		name: "simple",
+		data: `{
+	"@type": "cayley:TestStep",
+	"linkedql:limit": 10
+}`,
+		exp: &TestStep{Limit: 10},
+	},
+	{
+		name: "simple",
+		data: `{
+	"@type": "cayley:TestStep",
+	"linkedql:tags": ["a", "b"]
+}`,
+		exp: &TestStep{Tags: []string{"a", "b"}},
+	},
+	{
+		name: "nested",
+		data: `{
+	"@type": "cayley:TestStep",
+	"linkedql:limit": 10,
+	"linkedql:from": {
+		"@type": "cayley:TestStep",
+		"linkedql:limit": 15,
+		"linkedql:from": {
+			"@type": "cayley:TestStep",
+			"linkedql:limit": 20
+		}
+	}
+}`,
+		exp: &TestStep{
+			Limit: 10,
+			From: &TestStep{
+				Limit: 15,
+				From: &TestStep{
+					Limit: 20,
+				},
+			},
+		},
+	},
+	{
+		name: "nested slice",
+		data: `{
+	"@type": "cayley:TestStep",
+	"linkedql:limit": 10,
+	"linkedql:sub": [
+		{
+			"@type": "cayley:TestStep",
+			"linkedql:limit": 15
+		},
+		{
+			"@type": "cayley:TestStep",
+			"linkedql:limit": 20
+		}
+	]
+}`,
+		exp: &TestStep{
+			Limit: 10,
+			Sub: []PathStep{
+				&TestStep{
+					Limit: 15,
+				},
+				&TestStep{
+					Limit: 20,
+				},
+			},
+		},
+	},
+}
+
+type TestStep struct {
+	Limit int        `json:"limit"`
+	Tags  []string   `json:"tags"`
+	From  PathStep   `json:"from"`
+	Sub   []PathStep `json:"sub"`
+}
+
+func (s *TestStep) Type() quad.IRI {
+	return "cayley:TestStep"
+}
+
+func (s *TestStep) Description() string {
+	return "A TestStep for checking the registry"
+}
+
+func (s *TestStep) BuildIterator(qs graph.QuadStore) (query.Iterator, error) {
+	panic("Can't build iterator for TestStep")
+}
+
+func (s *TestStep) BuildPath(qs graph.QuadStore) (*path.Path, error) {
+	panic("Can't build path for TestStep")
+}
+
+func TestUnmarshalStep(t *testing.T) {
+	for _, c := range unmarshalCases {
+		t.Run(c.name, func(t *testing.T) {
+			s, err := Unmarshal([]byte(c.data))
+			require.NoError(t, err)
+			require.Equal(t, c.exp, s)
+		})
+	}
+}
diff --git a/query/linkedql/steps_test.go b/query/linkedql/steps_test.go
new file mode 100644
index 0000000..a20db41
--- /dev/null
+++ b/query/linkedql/steps_test.go
@@ -0,0 +1,593 @@
+package linkedql
+
+import (
+	"context"
+	"testing"
+
+	"github.com/cayleygraph/cayley/graph/memstore"
+	"github.com/cayleygraph/quad"
+	"github.com/stretchr/testify/require"
+)
+
+var singleQuadData = []quad.Quad{
+	quad.MakeIRI("alice", "likes", "bob", ""),
+}
+
+var testCases = []struct {
+	name    string
+	data    []quad.Quad
+	query   IteratorStep
+	results []interface{}
+}{
+	{
+		name:  "All Entities",
+		data:  singleQuadData,
+		query: &Entities{Identifiers: []EntityIdentifier{EntityIdentifierString("alice")}},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name:  "All Vertices",
+		data:  singleQuadData,
+		query: &Vertex{Values: nil},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "likes"},
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "Select",
+		data: singleQuadData,
+		query: &Select{
+			From: &As{
+				From: &Visit{
+					From: &As{
+						From: &Vertex{},
+						Name: "liker",
+					},
+					Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+				},
+				Name: "liked",
+			},
+		},
+		results: []interface{}{
+			map[string]interface{}{
+				"liker": map[string]string{"@id": "alice"},
+				"liked": map[string]string{"@id": "bob"},
+			},
+		},
+	},
+	{
+		name: "Select with tags",
+		data: singleQuadData,
+		query: &Select{
+			Tags: []string{"liker"},
+			From: &As{
+				From: &Visit{
+					From: &As{
+						From: &Vertex{},
+						Name: "liker",
+					},
+					Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+				},
+				Name: "liked",
+			},
+		},
+		results: []interface{}{
+			map[string]interface{}{
+				"liker": map[string]string{"@id": "alice"},
+			},
+		},
+	},
+	{
+		name: "Back",
+		data: singleQuadData,
+		query: &Back{
+			From: &Visit{
+				From: &Vertex{
+					Values: []quad.Value{quad.IRI("alice")},
+				},
+				Properties: PropertyPath{&Vertex{
+					Values: []quad.Value{
+						quad.IRI("likes"),
+					},
+				}},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "Both",
+		data: []quad.Quad{
+			quad.MakeIRI("alice", "likes", "bob", ""),
+			quad.MakeIRI("bob", "likes", "dan", ""),
+		},
+		query: &Both{
+			From: &Vertex{
+				Values: []quad.Value{quad.IRI("bob")},
+			},
+			Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "dan"},
+		},
+	},
+	{
+		name: "Count",
+		data: singleQuadData,
+		query: &Count{
+			From: &Vertex{Values: []quad.Value{}},
+		},
+		results: []interface{}{
+			map[string]string{"@value": "4", "@type": "xsd:integer"},
+		},
+	},
+	{
+		name: "Difference",
+		data: singleQuadData,
+		query: &Difference{
+			From: &Vertex{
+				Values: []quad.Value{quad.IRI("alice"), quad.IRI("likes")},
+			},
+			Steps: []PathStep{
+				&Vertex{
+					Values: []quad.Value{quad.IRI("likes")},
+				},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "Filter RegExp",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.String("Alice"), Label: nil},
+		},
+		query: &Filter{
+			From:   &Vertex{Values: []quad.Value{}},
+			Filter: &RegExp{Pattern: "A"},
+		},
+		results: []interface{}{
+			"Alice",
+		},
+	},
+	{
+		name: "Filter Like",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.String("Alice"), Label: nil},
+		},
+		query: &Filter{
+			From:   &Vertex{Values: []quad.Value{}},
+			Filter: &Like{Pattern: "a%"},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "Filter LessThan",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(0), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(1), Label: nil},
+		},
+		query: &LessThan{
+			From:  &Vertex{Values: []quad.Value{}},
+			Value: quad.Int(1),
+		},
+		results: []interface{}{
+			map[string]string{"@value": "0", "@type": "xsd:integer"},
+		},
+	},
+	{
+		name: "Filter GreaterThan",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(0), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(1), Label: nil},
+		},
+		query: &GreaterThan{
+			From:  &Vertex{Values: []quad.Value{}},
+			Value: quad.Int(0),
+		},
+		results: []interface{}{
+			map[string]string{"@value": "1", "@type": "xsd:integer"},
+		},
+	},
+	{
+		name: "Filter LessThanEquals",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(-1), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(0), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(1), Label: nil},
+		},
+		query: &LessThanEquals{
+			From:  &Vertex{Values: []quad.Value{}},
+			Value: quad.Int(0),
+		},
+		results: []interface{}{
+			map[string]string{"@value": "-1", "@type": "xsd:integer"},
+			map[string]string{"@value": "0", "@type": "xsd:integer"},
+		},
+	},
+	{
+		name: "Filter GreaterThanEquals",
+		data: []quad.Quad{
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(0), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(1), Label: nil},
+			{Subject: quad.IRI("alice"), Predicate: quad.IRI("name"), Object: quad.Int(2), Label: nil},
+		},
+		query: &GreaterThanEquals{
+			From:  &Vertex{Values: []quad.Value{}},
+			Value: quad.Int(1),
+		},
+		results: []interface{}{
+			map[string]string{"@value": "1", "@type": "xsd:integer"},
+			map[string]string{"@value": "2", "@type": "xsd:integer"},
+		},
+	},
+	{
+		name: "Has",
+		data: singleQuadData,
+		query: &Has{
+			From: &Vertex{
+				Values: []quad.Value{},
+			},
+			Property: PropertyPath{&Vertex{
+				Values: []quad.Value{quad.IRI("likes")},
+			}},
+			Values: []quad.Value{quad.IRI("bob")},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "HasReverse",
+		data: singleQuadData,
+		query: &HasReverse{
+			From: &Vertex{
+				Values: []quad.Value{},
+			},
+			Property: PropertyPath{&Vertex{
+				Values: []quad.Value{quad.IRI("likes")},
+			}},
+			Values: []quad.Value{quad.IRI("alice")},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "ViewReverse",
+		data: singleQuadData,
+		query: &VisitReverse{
+			From:       &Vertex{Values: []quad.Value{}},
+			Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "PropertyNames",
+		data: singleQuadData,
+		query: &PropertyNames{
+			From: &Vertex{Values: []quad.Value{}},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "likes"},
+		},
+	},
+	{
+		name: "Intersect",
+		data: []quad.Quad{
+			quad.MakeIRI("bob", "likes", "alice", ""),
+			quad.MakeIRI("dani", "likes", "alice", ""),
+		},
+		query: &Intersect{
+			From: &Visit{
+				From: &Vertex{Values: []quad.Value{quad.IRI("bob")}},
+				Properties: PropertyPath{&Vertex{
+					Values: []quad.Value{quad.IRI("likes")},
+				}},
+			},
+			Steps: []PathStep{
+				&Visit{
+					From:       &Vertex{Values: []quad.Value{quad.IRI("bob")}},
+					Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+				},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+		},
+	},
+	{
+		name: "Is",
+		data: singleQuadData,
+		query: &Is{
+			Values: []quad.Value{quad.IRI("bob")},
+			From: &Visit{
+				From: &Vertex{Values: []quad.Value{quad.IRI("alice")}},
+				Properties: PropertyPath{&Vertex{
+					Values: []quad.Value{quad.IRI("likes")},
+				}},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "Limit",
+		data: singleQuadData,
+		query: &Limit{
+			Limit: 2,
+			From: &Vertex{
+				Values: []quad.Value{},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "likes"},
+		},
+	},
+	{
+		name: "View",
+		data: singleQuadData,
+		query: &Visit{
+			From:       &Vertex{Values: []quad.Value{}},
+			Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "PropertyNames",
+		data: singleQuadData,
+		query: &PropertyNames{
+			From: &Vertex{Values: []quad.Value{}},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "likes"},
+		},
+	},
+	{
+		name: "Properties",
+		data: singleQuadData,
+		query: &Select{
+			From: &Properties{
+				From:  &Vertex{Values: []quad.Value{}},
+				Names: []quad.IRI{quad.IRI("likes")},
+			},
+		},
+		results: []interface{}{map[string]interface{}{"likes": map[string]string{"@id": "bob"}}},
+	},
+	{
+		name: "ReversePropertyNamesAs",
+		data: singleQuadData,
+		query: &Select{
+			From: &ReversePropertyNamesAs{
+				From: &Vertex{Values: []quad.Value{}},
+				Tag:  "predicate",
+			},
+		},
+		results: []interface{}{map[string]interface{}{"predicate": map[string]string{"@id": "likes"}}},
+	},
+	{
+		name: "PropertyNamesAs",
+		data: singleQuadData,
+		query: &Select{
+			From: &PropertyNamesAs{
+				From: &Vertex{Values: []quad.Value{}},
+				Tag:  "predicate",
+			},
+		},
+		results: []interface{}{map[string]interface{}{"predicate": map[string]string{"@id": "likes"}}},
+	},
+	{
+		name: "ReverseProperties",
+		data: singleQuadData,
+		query: &Select{
+			From: &ReverseProperties{
+				From:  &Vertex{Values: []quad.Value{}},
+				Names: []quad.IRI{quad.IRI("likes")},
+			},
+		},
+		results: []interface{}{map[string]interface{}{"likes": map[string]string{"@id": "alice"}}},
+	},
+	{
+		name: "Skip",
+		data: singleQuadData,
+		query: &Skip{
+			Offset: 2,
+			From: &Vertex{
+				Values: []quad.Value{},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "Union",
+		data: singleQuadData,
+		query: &Union{
+			From: &Vertex{
+				Values: []quad.Value{quad.IRI("alice")},
+			},
+			Steps: []PathStep{
+				&Vertex{
+					Values: []quad.Value{quad.IRI("bob")},
+				},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "SelectFirst",
+		data: singleQuadData,
+		query: &SelectFirst{
+			From: &As{
+				From: &Visit{
+					From: &As{
+						Name: "liker",
+						From: &Vertex{},
+					},
+					Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+				},
+				Name: "liked",
+			},
+		},
+		results: []interface{}{map[string]interface{}{"liked": map[string]string{"@id": "bob"}, "liker": map[string]string{"@id": "alice"}}},
+	},
+	{
+		name: "Unique",
+		data: singleQuadData,
+		query: &Unique{
+			From: &Vertex{
+				Values: []quad.Value{quad.IRI("alice"), quad.IRI("alice"), quad.IRI("bob")},
+			},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "bob"},
+		},
+	},
+	{
+		name: "Order",
+		data: singleQuadData,
+		query: &Order{
+			From: &Vertex{},
+		},
+		results: []interface{}{
+			map[string]string{"@id": "alice"},
+			map[string]string{"@id": "bob"},
+			map[string]string{"@id": "likes"},
+		},
+	},
+	{
+		name: "Optional",
+		data: []quad.Quad{
+			quad.MakeIRI("alice", "likes", "bob", ""),
+			quad.MakeIRI("alice", "name", "Alice", ""),
+			quad.MakeIRI("bob", "name", "Bob", ""),
+		},
+		query: &Select{
+			From: &Optional{
+				From: &Properties{
+					From:  &Vertex{Values: []quad.Value{}},
+					Names: []quad.IRI{quad.IRI("name")},
+				},
+				Step: &Properties{
+					From:  &Placeholder{},
+					Names: []quad.IRI{quad.IRI("likes")},
+				},
+			},
+		},
+		results: []interface{}{
+			map[string]interface{}{
+				"likes": map[string]string{"@id": "bob"},
+				"name":  map[string]string{"@id": "Alice"},
+			},
+			map[string]interface{}{
+				"name": map[string]string{"@id": "Bob"},
+			},
+		},
+	},
+	{
+		name: "Where",
+		data: []quad.Quad{
+			quad.MakeIRI("alice", "likes", "bob", ""),
+			quad.MakeIRI("alice", "name", "Alice", ""),
+			quad.MakeIRI("bob", "name", "Bob", ""),
+		},
+		query: &Select{
+			From: &As{
+				From: &Where{
+					From: &Vertex{},
+					Steps: []PathStep{
+						&As{
+							From: &Visit{
+								From: &Visit{
+									From:       &Placeholder{},
+									Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("likes")}}},
+								},
+								Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("name")}}},
+							},
+							Name: "likesName",
+						},
+						&As{
+							From: &Visit{
+								From:       &Placeholder{},
+								Properties: PropertyPath{&Vertex{Values: []quad.Value{quad.IRI("name")}}},
+							},
+							Name: "name",
+						},
+					},
+				},
+				Name: "person",
+			},
+		},
+		results: []interface{}{
+			map[string]interface{}{
+				"person":    map[string]string{"@id": "alice"},
+				"name":      map[string]string{"@id": "Alice"},
+				"likesName": map[string]string{"@id": "Bob"},
+			},
+		},
+	},
+	{
+		name: "Documents",
+		data: []quad.Quad{
+			quad.MakeIRI("alice", "likes", "bob", ""),
+			quad.MakeIRI("alice", "name", "Alice", ""),
+			quad.MakeIRI("bob", "name", "Bob", ""),
+			quad.MakeIRI("bob", "likes", "alice", ""),
+		},
+		query: &Documents{
+			From: &Properties{
+				From:  &Vertex{Values: []quad.Value{}},
+				Names: []quad.IRI{quad.IRI("name"), quad.IRI("likes")},
+			},
+		},
+		results: []interface{}{
+			map[string]interface{}{
+				"@id":   "alice",
+				"name":  []interface{}{map[string]string{"@id": "Alice"}},
+				"likes": []interface{}{map[string]string{"@id": "bob"}},
+			},
+			map[string]interface{}{
+				"@id":   "bob",
+				"name":  []interface{}{map[string]string{"@id": "Bob"}},
+				"likes": []interface{}{map[string]string{"@id": "alice"}},
+			},
+		},
+	},
+}
+
+func TestLinkedQL(t *testing.T) {
+	for _, c := range testCases {
+		t.Run(c.name, func(t *testing.T) {
+			store := memstore.New(c.data...)
+			ctx := context.TODO()
+			iterator, err := c.query.BuildIterator(store)
+			require.NoError(t, err)
+			var results []interface{}
+			for iterator.Next(ctx) {
+				results = append(results, iterator.Result())
+			}
+			require.NoError(t, iterator.Err())
+			require.Equal(t, c.results, results)
+		})
+	}
+}
diff --git a/server/http/api_v2_test.go b/server/http/api_v2_test.go
index e2dc7d3..d2d556e 100644
--- a/server/http/api_v2_test.go
+++ b/server/http/api_v2_test.go
@@ -1,16 +1,19 @@
 package cayleyhttp
 
 import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"net/http"
 	"net/http/httptest"
 	"sort"
 	"testing"
 
-	"github.com/cayleygraph/cayley/client"
 	"github.com/cayleygraph/cayley/graph"
-	"github.com/cayleygraph/cayley/graph/graphtest"
 	"github.com/cayleygraph/cayley/graph/memstore"
 	"github.com/cayleygraph/cayley/writer"
 	"github.com/cayleygraph/quad"
+	"github.com/cayleygraph/quad/jsonld"
 	"github.com/stretchr/testify/require"
 )
 
@@ -21,46 +24,70 @@ func makeHandle(t testing.TB, quads ...quad.Quad) *graph.Handle {
 	return &graph.Handle{qs, wr}
 }
 
-func makeServerV2(t testing.TB, quads ...quad.Quad) (string, func()) {
+func makeServerV2(t testing.TB, quads ...quad.Quad) *APIv2 {
 	h := makeHandle(t, quads...)
+	return NewAPIv2(h)
+}
+
+func writeQuads(q []quad.Quad, w io.Writer) error {
+	writer := jsonld.NewWriter(w)
+	reader := quad.NewReader(quads)
+	_, err := quad.Copy(writer, reader)
+	writer.Close()
+	return err
+}
+
+var mime = quad.FormatByName("jsonld").Mime[0]
 
-	api2 := NewAPIv2(h)
-	srv := httptest.NewServer(api2)
-	addr := srv.Listener.Addr()
-	return "http://" + addr.String(), func() {
-		srv.Close()
-		h.Close()
-	}
+var quads = []quad.Quad{
+	quad.MakeIRI("http://example.com/bob", "http://example.com/likes", "http://example.com/alice", ""),
+	quad.MakeIRI("http://example.com/alice", "http://example.com/likes", "http://example.com/bob", ""),
 }
 
 func TestV2Write(t *testing.T) {
-	addr, closer := makeServerV2(t)
-	defer closer()
+	api := makeServerV2(t)
+	buf := bytes.NewBuffer(nil)
 
-	quads := graphtest.MakeQuadSet()
-	cli := client.New(addr)
-	qw, err := cli.QuadWriter()
+	err := writeQuads(quads, buf)
 	require.NoError(t, err)
-	defer qw.Close()
-	n, err := quad.Copy(qw, quad.NewReader(quads))
-	require.NoError(t, err)
-	require.Equal(t, int(len(quads)), n)
-	err = qw.Close()
+
+	req, err := http.NewRequest(http.MethodGet, prefix+"/write", buf)
 	require.NoError(t, err)
+	req.Header.Set(hdrContentType, mime)
+
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(api.ServeWrite)
+	handler.ServeHTTP(rr, req)
+
+	require.Equal(t, rr.Code, http.StatusOK, rr.Body.String())
+
+	expectedResponse := newWriteResponse(len(quads))
+
+	var response writeResponse
+	json.Unmarshal(rr.Body.Bytes(), &response)
+
+	require.Equal(t, expectedResponse, response)
 }
 
 func TestV2Read(t *testing.T) {
-	expect := graphtest.MakeQuadSet()
-	addr, closer := makeServerV2(t, expect...)
-	defer closer()
+	api := makeServerV2(t, quads...)
+	buf := bytes.NewBuffer(nil)
 
-	cli := client.New(addr)
-	qr, err := cli.QuadReader()
+	req, err := http.NewRequest(http.MethodGet, prefix+"/read", buf)
 	require.NoError(t, err)
-	defer qr.Close()
-	quads, err := quad.ReadAll(qr)
+	req.Header.Set(hdrAccept, mime)
+
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(api.ServeRead)
+	handler.ServeHTTP(rr, req)
+
+	require.Equal(t, rr.Code, http.StatusOK, rr.Body.String())
+
+	reader := jsonld.NewReader(rr.Body)
+	receivedQuads, err := quad.ReadAll(reader)
 	require.NoError(t, err)
+	sort.Sort(quad.ByQuadString(receivedQuads))
 	sort.Sort(quad.ByQuadString(quads))
-	sort.Sort(quad.ByQuadString(expect))
-	require.Equal(t, expect, quads)
+	require.Equal(t, quads, receivedQuads)
+
 }