cayleygraph / cayley

An open-source graph database
https://cayley.io
Apache License 2.0
14.85k stars 1.25k forks source link

`LoadToDepth` from `schema` with property on depth 0 and depth 1 crashes #952

Open oxisto opened 3 years ago

oxisto commented 3 years ago

Description

I am trying to build up an ontology of a service, its endpoint and a TLS configuration and trying to persist it into Cayley and then load it again. If I try to load it again into objects, there seems to be some kind of type confusion between objects on depth 0 and depth 1, resulting into the crash below.

package main

import (
    "context"
    "fmt"
    "log"
    "math/rand"

    "github.com/cayleygraph/cayley"
    "github.com/cayleygraph/cayley/graph"
    "github.com/cayleygraph/cayley/schema"
    "github.com/cayleygraph/quad"
    "github.com/cayleygraph/quad/voc"
)

type TlsConfiguration struct {
    rdfType struct{} `quad:"@type > ex:TlsConfig"`

    Version string `quad:"ex:version"`
}

type HttpEndpoint struct {
    rdfType struct{} `quad:"@type > ex:Http"`

    URL       string           `quad:"ex:url"`
    TlsConfig TlsConfiguration `quad:"ex:tls"`
}

type Category struct {
    Name string `quad:"ex:name"`
}

type Service struct {
    rdfType struct{} `quad:"@type > ex:Service"`

    ID           quad.IRI     `json:"@id"`
    HttpEndpoint HttpEndpoint `quad:"ex:http"`
    Category     Category     `quad:"ex:category"`
}

func main() {
    store, err := cayley.NewMemoryGraph()
    if err != nil {
        log.Fatalln(err)
    }

    voc.RegisterPrefix("ex:", "https://example.io")

    sch := schema.NewConfig()
    // Override a function to generate IDs. Can be changed to generate UUIDs, for example.
    sch.GenerateID = func(_ interface{}) quad.Value {
        return quad.BNode(fmt.Sprintf("node%d", rand.Intn(1000)))
    }

    qw := graph.NewWriter(store)

    s1 := &Service{
        ID: quad.IRI("s1"),
        HttpEndpoint: HttpEndpoint{
            URL: "https://example.io",
            TlsConfig: TlsConfiguration{
                Version: "1.2",
            },
        },
        Category: Category{Name: "An example service"},
    }

    sch.WriteAsQuads(qw, s1)

    s2 := &Service{
        ID: quad.IRI("s2"),
        HttpEndpoint: HttpEndpoint{
            URL: "https://cayley.io",
            TlsConfig: TlsConfiguration{
                Version: "1.3",
            },
        },
        Category: Category{Name: "A graph service"},
    }

    sch.WriteAsQuads(qw, s2)

    qw.Close()

    // Print quads
    fmt.Println("\nquads:")
    it := store.QuadsAllIterator().Iterate()
    defer it.Close()

    for it.Next(context.TODO()) {
        fmt.Println(store.Quad(it.Result()))
    }

    var services []Service
    sch.LoadToDepth(context.TODO(), store, &services, 1) // depth=0 will work, 1 will crash

    for _, v := range services {
        fmt.Printf("%+v\n", v)
    }
}

Steps to reproduce the issue:

  1. Build up a schema with objects on depth 0 and depth 1
  2. Store it
  3. Load it

Received results:

<s1> -- <rdf:type> -> <ex:Actor>
_:node81 -- <rdf:type> -> <ex:Http>
_:node81 -- <ex:url> -> "https://example.io"
_:node887 -- <rdf:type> -> <ex:TlsConfig>
_:node887 -- <ex:version> -> "1.2"
_:node81 -- <ex:tls> -> _:node887
<s1> -- <ex:http> -> _:node81
_:node847 -- <ex:name> -> "An example service"
<s1> -- <ex:category> -> _:node847
<s2> -- <rdf:type> -> <ex:Actor>
_:node59 -- <rdf:type> -> <ex:Http>
_:node59 -- <ex:url> -> "https://cayley.io"
_:node81 -- <rdf:type> -> <ex:TlsConfig>
_:node81 -- <ex:version> -> "1.3"
_:node59 -- <ex:tls> -> _:node81
<s2> -- <ex:http> -> _:node59
_:node318 -- <ex:name> -> "A graph service"
<s2> -- <ex:category> -> _:node318

panic: reflect.Set: value of type main.HttpEndpoint is not assignable to type main.TlsConfiguration
goroutine 1 [running]:

reflect.Value.assignTo(0x100505fc0, 0x140001468a0, 0x199, 0x1004ce012, 0xb, 0x100500a60, 0x0, 0x0, 0x0, 0x0)
    /opt/homebrew/Cellar/go/1.16.3/libexec/src/reflect/value.go:2451 +0x3f0
reflect.Value.Set(0x100500a60, 0x140001055d0, 0x199, 0x100505fc0, 0x140001468a0, 0x199)
    /opt/homebrew/Cellar/go/1.16.3/libexec/src/reflect/value.go:1564 +0xa4
github.com/cayleygraph/cayley/schema.(*loader).loadIteratorToDepth(0x14000108a20, 0x100520230, 0x14000181b30, 0x100500a60, 0x140001055d0, 0x199, 0x0, 0x100520770, 0x140001217e8, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:470 +0x790
github.com/cayleygraph/cayley/schema.(*loader).loadToValue(0x14000108a20, 0x100520230, 0x14000181800, 0x100505fc0, 0x14000147320, 0x199, 0x1, 0x1400018ac48, 0x0, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:372 +0xe08
github.com/cayleygraph/cayley/schema.(*loader).loadIteratorToDepth(0x14000108a20, 0x100520230, 0x14000181800, 0x100505fc0, 0x14000147320, 0x199, 0x1, 0x100520770, 0x140001213f8, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:511 +0xd58
github.com/cayleygraph/cayley/schema.(*loader).loadToValue(0x14000108a20, 0x100520230, 0x140001096e0, 0x10050b840, 0x14000182780, 0x199, 0x2, 0x1400018b848, 0x0, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:372 +0xe08
github.com/cayleygraph/cayley/schema.(*loader).loadIteratorToDepth(0x14000108a20, 0x100520230, 0x140001096e0, 0x1004ef100, 0x140001203f0, 0x197, 0x2, 0x0, 0x0, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:511 +0xd58
github.com/cayleygraph/cayley/schema.(*Config).LoadIteratorToDepth(0x1400012c0c0, 0x1005201f8, 0x1400010e008, 0x100521398, 0x14000146000, 0x1004ea340, 0x140001203f0, 0x16, 0x2, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:142 +0xa8
github.com/cayleygraph/cayley/schema.(*Config).LoadToDepth(0x1400012c0c0, 0x1005201f8, 0x1400010e008, 0x100521398, 0x14000146000, 0x1004ea340, 0x140001203f0, 0x1, 0x0, 0x0, ...)
    /Users/oxisto/go/pkg/mod/github.com/cayleygraph/cayley@v0.7.7-0.20210518204410-08381efb7f81/schema/loader.go:116 +0x240
main.main()
    /Users/oxisto/Downloads/cayley-test/main.go:95 +0x750

Expected results:

I expect it to work - or at least give me a proper error message why it does not.

Output of cayley version or commit hash:

github.com/cayleygraph/cayley v0.7.7-0.20210518204410-08381efb7f81

Environment details:

Backend database: in-memory

oxisto commented 3 years ago

It seems to work if you add a @id annotation to the URL field of the HttpEndpoint, so that it is not a non-named node. I will try to create a PR that adds this caveat to the documentation and check whether we can avoid the panic.