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 Client #897

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
413c424
WIP
iddan Dec 1, 2019
13e3d54
Defined better API and loaded json schema to quad store
iddan Dec 1, 2019
7b13b02
Start implementing OWL module and test it
iddan Dec 2, 2019
a4a2bc7
Complete OWL module
iddan Dec 6, 2019
54fb844
Make union properties work
iddan Dec 7, 2019
49eb81e
Correct list containing
iddan Dec 7, 2019
a3ab0c4
Creating a basic file, types are still not correct
iddan Dec 7, 2019
38d9a37
Remove old client files
iddan Dec 7, 2019
c57f5f4
Rename client2 to generate_client.go
iddan Dec 7, 2019
5abd1d8
Share identifier vars
iddan Dec 7, 2019
dbf4493
Correct value types
iddan Dec 7, 2019
fce2367
Correct slice types
iddan Dec 7, 2019
e7219c4
Don't use Path ptr
iddan Dec 7, 2019
ce6b1c6
Correctly use path arguments
iddan Dec 7, 2019
bd77eb9
Correct path creation
iddan Dec 7, 2019
96f0a1c
Share more string values
iddan Dec 7, 2019
3b0d873
Change behaviour if doesn't have from
iddan Dec 7, 2019
56d3cdc
Correct hasFrom
iddan Dec 7, 2019
44d4f23
Add generate_client to gogen, remove gizmo codegen
iddan Dec 7, 2019
5c8cf50
Rename path.go to client.go
iddan Dec 7, 2019
8c7b5ed
Move code generation script
iddan Dec 11, 2019
1082099
Update path in gogen.go
iddan Dec 11, 2019
d6122b5
Refactor code to be more navigatable and panic only on main
iddan Dec 13, 2019
ef622ab
Generate comments
iddan Dec 13, 2019
13579de
linkedql: Add comment to path methods
iddan Dec 13, 2019
abd548c
linkedql: Correct type handling in client
iddan Dec 13, 2019
78ac6a2
owl: use constants from a new voc package
dennwc Dec 15, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
318 changes: 318 additions & 0 deletions cmd/generate_linkedql_client/generate_linkedql_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package main

import (
"bufio"
"context"
"encoding/json"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"os"

"github.com/cayleygraph/cayley/graph"
"github.com/cayleygraph/cayley/graph/memstore"
"github.com/cayleygraph/cayley/owl"
"github.com/cayleygraph/quad"
"github.com/cayleygraph/quad/jsonld"
"github.com/cayleygraph/quad/voc/rdfs"
)

const schemaFile = "linkedql.json"
const outputFilePath = "query/linkedql/client/client.go"

var stepIRI = quad.IRI("http://cayley.io/linkedql#Step")
var pathStepIRI = quad.IRI("http://cayley.io/linkedql#PathStep")
var iteratorStepIRI = quad.IRI("http://cayley.io/linkedql#IteratorStep")

func main() {
ctx := context.TODO()
qs, err := loadSchema()

if err != nil {
panic(err)
}

stepClass, err := owl.GetClass(ctx, qs, stepIRI)

if err != nil {
panic(err)
}

stepSubClasses := stepClass.SubClasses()
var decls []ast.Decl

for _, stepSubClass := range stepSubClasses {
if stepSubClass.Identifier == pathStepIRI || stepSubClass.Identifier == iteratorStepIRI {
continue
}
stepSubClassDecls, err := stepSubClassToDecls(stepSubClass)
if err != nil {
panic(err)
}
decls = append(decls, stepSubClassDecls...)
}

// Create a FileSet for node. Since the node does not come
// from a real source file, fset will be empty.
fset := token.NewFileSet()
file, err := getFile(fset)

if err != nil {
panic(err)
}

file.Decls = append(file.Decls, decls...)

err = writeFile(fset, file, outputFilePath)

if err != nil {
panic(err)
}
}

// loadSchema loads the schema file into an in-memory store
func loadSchema() (graph.QuadStore, error) {
jsonFile, err := os.Open(schemaFile)
if err != nil {
return nil, err
}
var o interface{}
qs := memstore.New()
json.NewDecoder(jsonFile).Decode(&o)
reader := jsonld.NewReaderFromMap(o)
for true {
quad, err := reader.ReadQuad()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
qs.AddQuad(quad)
}
return qs, nil
}

var xsdString = quad.IRI("http://www.w3.org/2001/XMLSchema#string")
var rdfsResource = quad.IRI(rdfs.Resource).Full()
var stringIdent = ast.NewIdent("string")

var pathTypeIdent = ast.NewIdent("Path")
var pathIdent = ast.NewIdent("p")

func stepSubClassToDecls(stepSubClass *owl.Class) ([]ast.Decl, error) {
var decls []ast.Decl
hasFrom := false
iri, ok := stepSubClass.Identifier.(quad.IRI)
if !ok {
return nil, fmt.Errorf("Unexpected class identifier %v of type %T", stepSubClass.Identifier, stepSubClass.Identifier)
}
properties := stepSubClass.Properties()

var paramsList []*ast.Field
for _, property := range properties {
_type, err := propertyToValueType(stepSubClass, property)
if err != nil {
return nil, err
}
ident := iriToIdent(property.Identifier)
if ident.Name == "from" {
hasFrom = true
continue
}
paramsList = append(paramsList, &ast.Field{
Names: []*ast.Ident{ident},
Type: _type,
})
}
elts := []ast.Expr{
&ast.KeyValueExpr{
Key: &ast.BasicLit{
Kind: token.STRING,
Value: "\"@type\"",
},
Value: &ast.BasicLit{
Kind: token.STRING,
Value: "\"" + string(iri) + "\"",
},
},
}
if hasFrom {
elts = append(elts, &ast.KeyValueExpr{
Key: &ast.BasicLit{
Kind: token.STRING,
Value: "\"from\"",
},
Value: pathIdent,
})
}

for _, property := range properties {
ident := iriToIdent(property.Identifier)
if ident.Name == "from" {
continue
}
var value ast.Expr
value = iriToIdent(property.Identifier)
elts = append(elts, &ast.KeyValueExpr{
Key: &ast.BasicLit{
Kind: token.STRING,
Value: "\"" + string(property.Identifier) + "\"",
},
Value: value,
})
}

var recv *ast.FieldList

if hasFrom {
recv = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{pathIdent},
Type: pathTypeIdent,
},
},
}
}

comment, err := stepSubClass.Comment()

var doc *ast.CommentGroup

if err == nil {
doc = &ast.CommentGroup{
List: []*ast.Comment{
{
Text: "// " + iriToStringIdent(iri) + " " + comment,
},
},
}
}

decls = append(decls, &ast.FuncDecl{
Name: iriToIdent(iri),
Doc: doc,
Type: &ast.FuncType{
Params: &ast.FieldList{List: paramsList},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Names: nil,
Type: pathTypeIdent,
},
},
},
},
Recv: recv,
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.ReturnStmt{
Results: []ast.Expr{
&ast.CompositeLit{
Type: pathTypeIdent,
Elts: elts,
},
},
},
},
},
})
return decls, nil
}

var quadValueType = &ast.SelectorExpr{
Sel: ast.NewIdent("Value"),
X: ast.NewIdent("quad"),
}

func propertyToValueType(class *owl.Class, property *owl.Property) (ast.Expr, error) {
_range, err := property.Range()
if err != nil {
return nil, err
}
isSlice := true
isPTR := false
cardinality, err := class.CardinalityOf(property)
if cardinality == int64(1) {
isSlice = false
isPTR = false
}
maxCardinality, err := class.MaxCardinalityOf(property)
if maxCardinality == int64(1) {
isSlice = false
isPTR = true
}
var t ast.Expr
if _range == xsdString {
t = stringIdent
} else if _range == pathStepIRI {
t = pathTypeIdent
} else if _range == rdfsResource {
t = quadValueType
} else {
return nil, fmt.Errorf("Unexpected range %v", _range)
}
if isPTR {
t = &ast.StarExpr{
X: t,
}
}
if isSlice {
t = &ast.ArrayType{
Elt: t,
}
}
return t, nil
}

func getFile(fset *token.FileSet) (*ast.File, error) {
src := `
package client

import (
"github.com/cayleygraph/quad"
)

type Path map[string]interface{}
`
file, err := parser.ParseFile(fset, "", src, 0)

if err != nil {
return nil, err
}

return file, nil
}

// writeFile writes given file of given fset to given path
func writeFile(fset *token.FileSet, file *ast.File, path string) error {
f, err := os.Create(path)

if err != nil {
return err
}

w := bufio.NewWriter(f)

err = format.Node(w, fset, file)

if err != nil {
return err
}

w.Flush()
f.Close()

return nil
}

func iriToStringIdent(iri quad.IRI) string {
return string(iri)[26:]
}

func iriToIdent(iri quad.IRI) *ast.Ident {
return ast.NewIdent(iriToStringIdent(iri))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.0
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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ 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 h1:vqf+71ZINP3eSbtaEzpey0HTr9p4M2xHdmVCda8D7+Q=
github.com/cayleygraph/quad v1.2.0/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=
Expand Down
2 changes: 1 addition & 1 deletion gogen.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package cayley

//go:generate go run ./cmd/docgen/docgen.go -i ./docs/GizmoAPI.md.in -o ./docs/GizmoAPI.md
//go:generate go run ./cmd/generate_linkedql_client/generate_linkedql_client.go
19 changes: 18 additions & 1 deletion internal/linkedql/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,23 @@ func (g *generator) Generate() []byte {
Range: rng,
})
}
graph := []interface{}{
map[string]string{
"@id": "linkedql:Step",
"@type": "owl:Class",
},
map[string]interface{}{
"@id": "linkedql:PathStep",
"@type": "owl:Class",
"rdfs:subClassOf": map[string]string{"@id": "linkedql:Step"},
},
map[string]interface{}{
"@id": "linkedql:IteratorStep",
"@type": "owl:Class",
"rdfs:subClassOf": map[string]string{"@id": "linkedql:Step"},
},
}
graph = append(graph, g.out...)
data, err := json.Marshal(map[string]interface{}{
"@context": map[string]interface{}{
"rdf": map[string]string{"@id": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"},
Expand All @@ -258,7 +275,7 @@ func (g *generator) Generate() []byte {
"xsd": map[string]string{"@id": "http://www.w3.org/2001/XMLSchema#"},
"linkedql": map[string]string{"@id": "http://cayley.io/linkedql#"},
},
"@graph": g.out,
"@graph": graph,
})
if err != nil {
panic(err)
Expand Down