Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LinkedQL: Full Linked Model #931

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 30 additions & 29 deletions query/linkedql/iter_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package linkedql
import (
"context"

"github.com/cayleygraph/cayley/graph/refs"
"github.com/cayleygraph/cayley/query"
"github.com/cayleygraph/cayley/query/path"
"github.com/cayleygraph/quad"
"github.com/piprate/json-gold/ld"
)

Expand All @@ -13,46 +16,43 @@ var (

// DocumentIterator is an iterator of documents from the graph
type DocumentIterator struct {
tagsIt *TagsIterator
dataset *ld.RDFDataset
quadIt *QuadIterator
quads []quad.Quad
err error
exhausted bool
}

// 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, exhausted: false}
// NewDocumentIterator constructs a DocumentIterator for a Namer and Path
func NewDocumentIterator(namer refs.Namer, path *path.Path) *DocumentIterator {
quadIt := NewQuadIterator(namer, path, nil)
return NewDocumentIteratorFromQuadIterator(quadIt)
}

func (it *DocumentIterator) getDataset(ctx context.Context) (*ld.RDFDataset, error) {
d := ld.NewRDFDataset()
for it.tagsIt.Next(ctx) {
r := it.tagsIt.ValueIt.scanner.Result()
if err := it.tagsIt.Err(); err != nil {
if err != nil {
return nil, err
}
}
if r == nil {
continue
}
err := it.tagsIt.addResultsToDataset(d, r)
// NewDocumentIteratorFromQuadIterator constructs DocumentIterator from a QuadIterator
func NewDocumentIteratorFromQuadIterator(quadIt *QuadIterator) *DocumentIterator {
return &DocumentIterator{quadIt: quadIt, exhausted: false}
}

func (it *DocumentIterator) getQuads(ctx context.Context) ([]quad.Quad, error) {
var allQuads []quad.Quad
for it.quadIt.Next(ctx) {
quads, err := it.quadIt.resultQuads()
if err != nil {
return nil, err
}
allQuads = append(allQuads, quads...)
}
return d, nil
return allQuads, nil
}

// Next implements query.Iterator.
func (it *DocumentIterator) Next(ctx context.Context) bool {
if !it.exhausted {
d, err := it.getDataset(ctx)
quads, err := it.getQuads(ctx)
if err != nil {
it.err = err
} else {
it.dataset = d
it.quads = quads
}
it.exhausted = true
return true
Expand All @@ -64,7 +64,11 @@ func (it *DocumentIterator) Next(ctx context.Context) bool {
func (it *DocumentIterator) Result() interface{} {
context := make(map[string]interface{})
opts := ld.NewJsonLdOptions("")
c, err := datasetToCompact(it.dataset, context, opts)
d, err := quadsToDataset(it.quads)
if err != nil {
it.err = err
}
c, err := datasetToCompact(d, context, opts)
if err != nil {
it.err = err
}
Expand All @@ -73,19 +77,16 @@ func (it *DocumentIterator) Result() interface{} {

// Err implements query.Iterator.
func (it *DocumentIterator) Err() error {
if it.tagsIt == nil {
return nil
}
if it.err != nil {
return it.err
}
return it.tagsIt.Err()
return it.quadIt.Err()
}

// Close implements query.Iterator.
func (it *DocumentIterator) Close() error {
if it.tagsIt == nil {
if it.quadIt == nil {
return nil
}
return it.tagsIt.Close()
return it.quadIt.Close()
}
166 changes: 166 additions & 0 deletions query/linkedql/iter_quads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package linkedql

import (
"context"
"fmt"

"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"
"github.com/piprate/json-gold/ld"
)

var _ query.Iterator = (*QuadIterator)(nil)

// QuadIterator is an iterator that returns quads of the results of a query
type QuadIterator struct {
Namer refs.Namer
Path *path.Path
Properties PropertyIRIs
scanner iterator.Scanner
err error
}

// NewQuadIterator creates a new QuadIterator
func NewQuadIterator(namer refs.Namer, path *path.Path, properties PropertyIRIs) *QuadIterator {
return &QuadIterator{
Namer: namer,
Path: path,
Properties: properties,
scanner: nil,
err: nil,
}
}

// createScanner creates a scanner for the iterator if not created before
func (it *QuadIterator) createScanner(ctx context.Context) {
if it.scanner == nil {
it.scanner = it.Path.BuildIterator(ctx).Iterate()
}
}

// Next implements query.Iterator.
func (it *QuadIterator) Next(ctx context.Context) bool {
it.createScanner(ctx)
return it.scanner.Next(ctx)
}

func toSubject(namer refs.Namer, result refs.Ref) (quad.Identifier, error) {
v := namer.NameOf(result)
id, ok := v.(quad.Identifier)
if !ok {
return nil, fmt.Errorf("Expected subject to be an entity identifier but instead received: %v", v)
}
return id, nil
}

func toQuad(namer refs.Namer, subject quad.Value, tag string, ref refs.Ref) quad.Quad {
p := quad.IRI(tag)
o := namer.NameOf(ref)
return quad.Quad{Subject: subject, Predicate: p, Object: o}
}

func (it *QuadIterator) resultQuads() ([]quad.Quad, error) {
r := it.scanner.Result()
if r == nil {
return nil, nil
}
s, err := toSubject(it.Namer, r)
if err != nil {
return nil, err
}
var quads []quad.Quad
refTags := make(map[string]refs.Ref)
it.scanner.TagResults(refTags)
if len(refTags) == 0 {
quads = append(quads, MakeSingleEntityQuad(s))
} else if len(it.Properties) == 0 {
for tag, ref := range refTags {
p := quad.IRI(tag)
o := it.Namer.NameOf(ref)
q := quad.Quad{Subject: s, Predicate: p, Object: o}
quads = append(quads, q)
}
} else {
for _, p := range it.Properties {
ref := refTags[string(p)]
o := it.Namer.NameOf(ref)
q := quad.Quad{Subject: s, Predicate: quad.IRI(p), Object: o}
quads = append(quads, q)
}
}
return quads, nil
}

func quadsToDataset(quads []quad.Quad) (*ld.RDFDataset, error) {
d := ld.NewRDFDataset()
// TODO(iddan): reuse code from quad
for _, q := range quads {
var graph string
if q.Label != nil {
graph = q.Label.String()
} else {
graph = "@default"
}
s, err := jsonld.ToNode(q.Subject)
if err != nil {
return nil, err
}
p, err := jsonld.ToNode(q.Predicate)
if err != nil {
return nil, err
}
o, err := jsonld.ToNode(q.Object)
if err != nil {
return nil, err
}
d.Graphs[graph] = append(d.Graphs[graph], ld.NewQuad(
s,
p,
o,
graph,
))
}
return d, nil
}

func (it *QuadIterator) resultJSONLD() (interface{}, error) {
quads, err := it.resultQuads()
if err != nil {
return nil, err
}
d, err := quadsToDataset(quads)
if err != nil {
return nil, err
}
doc, err := singleDocumentFromRDF(d)
if err != nil {
return nil, err
}
return doc, nil
}

// Result implements quad.Iterator
func (it *QuadIterator) Result() interface{} {
r, err := it.resultJSONLD()
if err != nil {
it.err = err
}
return r
}

// Err implements query.Iterator.
func (it *QuadIterator) Err() error {
if it.err != nil {
return it.err
}
return it.scanner.Err()
}

// Close implements query.Iterator.
func (it *QuadIterator) Close() error {
return it.scanner.Close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,24 @@ import (

"github.com/cayleygraph/cayley/graph/memstore"
"github.com/cayleygraph/quad"
"github.com/piprate/json-gold/ld"
"github.com/stretchr/testify/require"
)

var (
namespace = "http://example.com/"
alice = namespace + "alice"
likes = namespace + "likes"
alice = quad.IRI(namespace + "alice")
likes = quad.IRI(namespace + "likes")
blank = quad.RandomBlankNode()
name = namespace + "name"
name = quad.IRI(namespace + "name")
aliceName = quad.String("Alice")
aliceLikesBlank = quad.Quad{
Subject: quad.IRI(alice),
Predicate: quad.IRI(likes),
Subject: alice,
Predicate: likes,
Object: blank,
}
aliceNameAlice = quad.Quad{
Subject: quad.IRI(alice),
Predicate: quad.IRI(name),
Subject: alice,
Predicate: name,
Object: aliceName,
}
)
Expand All @@ -33,21 +32,21 @@ var testCases = []struct {
name string
data quad.Quad
value quad.Value
expected ld.Node
expected quad.Value
err error
}{
{
name: "Success for IRI",
data: aliceLikesBlank,
value: aliceLikesBlank.Subject,
expected: ld.NewIRI(alice),
expected: alice,
err: nil,
},
{
name: "Success for Blank Node",
data: aliceLikesBlank,
value: aliceLikesBlank.Object,
expected: ld.NewBlankNode(string(blank)),
expected: blank,
err: nil,
},
{
Expand Down
Loading